예제 #1
0
    def update(self, request, **kwargs):
        """Update the metadata of a version."""
        version: Version = self.get_object()

        # TODO @permission_required doesn't work on methods
        # https://github.com/django-guardian/django-guardian/issues/723
        response = get_40x_or_None(request, ['owner'],
                                   version.dandiset,
                                   return_403=True)
        if response:
            return response

        serializer = VersionMetadataSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        version_metadata: VersionMetadata
        version_metadata, created = VersionMetadata.objects.get_or_create(
            name=serializer.validated_data['name'],
            metadata=serializer.validated_data['metadata'])

        if created:
            version_metadata.save()

        version.metadata = version_metadata
        version.save()

        serializer = VersionDetailSerializer(instance=version)
        return Response(serializer.data, status=status.HTTP_200_OK)
예제 #2
0
    def create(self, request):
        serializer = VersionMetadataSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        version_metadata, created = VersionMetadata.objects.get_or_create(
            name=serializer.validated_data['name'],
            metadata=serializer.validated_data['metadata'],
        )
        if created:
            version_metadata.save()

        if 'identifier' in serializer.validated_data['metadata']:
            identifier = serializer.validated_data['metadata']['identifier']
            if identifier.startswith('DANDI:'):
                identifier = identifier[6:]
            try:
                dandiset = Dandiset(id=int(identifier))
            except ValueError:
                return Response(f'Invalid Identifier {identifier}', status=400)
        else:
            dandiset = Dandiset()
        try:
            # Without force_insert, Django will try to UPDATE an existing dandiset if one exists.
            # We want to throw an error if a dandiset already exists.
            dandiset.save(force_insert=True)
        except IntegrityError as e:
            # https://stackoverflow.com/questions/25368020/django-deduce-duplicate-key-exception-from-integrityerror
            # https://www.postgresql.org/docs/13/errcodes-appendix.html
            # Postgres error code 23505 == unique_violation
            if e.__cause__.pgcode == '23505':
                return Response(
                    f'Dandiset {dandiset.identifier} Already Exists',
                    status=400)
            raise e

        assign_perm('owner', request.user, dandiset)

        # Create new draft version
        version = Version(dandiset=dandiset,
                          metadata=version_metadata,
                          version='draft')
        version.save()

        serializer = DandisetDetailSerializer(instance=dandiset)
        return Response(serializer.data, status=status.HTTP_200_OK)
예제 #3
0
    def update(self, request, **kwargs):
        """Update the metadata of a version."""
        version: Version = self.get_object()
        if version.version != 'draft':
            return Response(
                'Only draft versions can be modified.',
                status=status.HTTP_405_METHOD_NOT_ALLOWED,
            )

        serializer = VersionMetadataSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        name = serializer.validated_data['name']
        metadata = serializer.validated_data['metadata']
        # Strip away any computed fields
        metadata = Version.strip_metadata(metadata)

        version.name = name
        version.metadata = metadata
        version.status = Version.Status.PENDING
        version.save()

        serializer = VersionDetailSerializer(instance=version)
        return Response(serializer.data, status=status.HTTP_200_OK)
예제 #4
0
class DandisetViewSet(ReadOnlyModelViewSet):
    permission_classes = [IsAuthenticatedOrReadOnly]
    serializer_class = DandisetDetailSerializer
    pagination_class = DandiPagination
    filter_backends = [filters.SearchFilter, DandisetFilterBackend]
    search_fields = ['versions__metadata__metadata']

    lookup_value_regex = Dandiset.IDENTIFIER_REGEX
    # This is to maintain consistency with the auto-generated names shown in swagger.
    lookup_url_kwarg = 'dandiset__pk'

    def get_queryset(self):
        # TODO: This will filter the dandisets list if there is a query parameter user=me.
        # This is not a great solution but it is needed for the My Dandisets page.
        queryset = Dandiset.objects.all().order_by('created')
        user_kwarg = self.request.query_params.get('user', None)
        if user_kwarg == 'me':
            return get_objects_for_user(self.request.user,
                                        'owner',
                                        queryset,
                                        with_superuser=False)
        return queryset

    def get_object(self):
        # Alternative to path converters, which DRF doesn't support
        # https://docs.djangoproject.com/en/3.0/topics/http/urls/#registering-custom-path-converters

        lookup_url = self.kwargs[self.lookup_url_kwarg]
        try:
            lookup_value = int(lookup_url)
        except ValueError:
            raise Http404('Not a valid identifier.')
        self.kwargs[self.lookup_url_kwarg] = lookup_value

        return super().get_object()

    @swagger_auto_schema(
        request_body=VersionMetadataSerializer(),
        responses={200: DandisetDetailSerializer()},
    )
    def create(self, request):
        serializer = VersionMetadataSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        version_metadata, created = VersionMetadata.objects.get_or_create(
            name=serializer.validated_data['name'],
            metadata=serializer.validated_data['metadata'],
        )
        if created:
            version_metadata.save()

        if 'identifier' in serializer.validated_data['metadata']:
            identifier = serializer.validated_data['metadata']['identifier']
            if identifier.startswith('DANDI:'):
                identifier = identifier[6:]
            try:
                dandiset = Dandiset(id=int(identifier))
            except ValueError:
                return Response(f'Invalid Identifier {identifier}', status=400)
        else:
            dandiset = Dandiset()
        try:
            # Without force_insert, Django will try to UPDATE an existing dandiset if one exists.
            # We want to throw an error if a dandiset already exists.
            dandiset.save(force_insert=True)
        except IntegrityError as e:
            # https://stackoverflow.com/questions/25368020/django-deduce-duplicate-key-exception-from-integrityerror
            # https://www.postgresql.org/docs/13/errcodes-appendix.html
            # Postgres error code 23505 == unique_violation
            if e.__cause__.pgcode == '23505':
                return Response(
                    f'Dandiset {dandiset.identifier} Already Exists',
                    status=400)
            raise e

        assign_perm('owner', request.user, dandiset)

        # Create new draft version
        version = Version(dandiset=dandiset,
                          metadata=version_metadata,
                          version='draft')
        version.save()

        serializer = DandisetDetailSerializer(instance=dandiset)
        return Response(serializer.data, status=status.HTTP_200_OK)

    # @permission_required_or_403('owner', (Dandiset, 'dandiset__pk'))
    def destroy(self, request, dandiset__pk):
        dandiset: Dandiset = get_object_or_404(Dandiset, pk=dandiset__pk)

        # TODO @permission_required doesn't work on methods
        # https://github.com/django-guardian/django-guardian/issues/723
        response = get_40x_or_None(request, ['owner'],
                                   dandiset,
                                   return_403=True)
        if response:
            return response

        dandiset.delete()
        return Response(None, status=status.HTTP_204_NO_CONTENT)

    @swagger_auto_schema(method='GET',
                         responses={200: UserSerializer(many=True)})
    @swagger_auto_schema(
        method='PUT',
        request_body=UserSerializer(many=True),
        responses={
            200: UserSerializer(many=True),
            400: 'User not found, or cannot remove all owners',
        },
    )
    # TODO move these into a viewset
    @action(methods=['GET', 'PUT'], detail=True)
    def users(self, request, dandiset__pk):
        dandiset = get_object_or_404(Dandiset, pk=dandiset__pk)
        if request.method == 'PUT':
            # Verify that the user is currently an owner
            response = get_40x_or_None(request, ['owner'],
                                       dandiset,
                                       return_403=True)
            if response:
                return response

            serializer = UserSerializer(data=request.data, many=True)
            serializer.is_valid(raise_exception=True)

            def get_user_or_400(username):
                try:
                    return User.objects.get(username=username)
                except User.DoesNotExist:
                    raise ValidationError(f'User {username} not found')

            owners = [
                get_user_or_400(username=owner['username'])
                for owner in serializer.validated_data
            ]
            if len(owners) < 1:
                raise ValidationError('Cannot remove all draft owners')

            removed_owners, added_owners = dandiset.set_owners(owners)
            dandiset.save()

            send_ownership_change_emails(dandiset, removed_owners,
                                         added_owners)

        serializer = UserSerializer(dandiset.owners, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)
예제 #5
0
class VersionViewSet(NestedViewSetMixin, DetailSerializerMixin,
                     ReadOnlyModelViewSet):
    queryset = Version.objects.all().select_related('dandiset').order_by(
        'created')

    serializer_class = VersionSerializer
    serializer_detail_class = VersionDetailSerializer
    pagination_class = DandiPagination

    lookup_field = 'version'
    lookup_value_regex = Version.VERSION_REGEX

    def get_queryset(self):
        # We need to check the dandiset to see if it's embargoed, and if so whether or not the
        # user has ownership
        dandiset = get_object_or_404(Dandiset, pk=self.kwargs['dandiset__pk'])
        if dandiset.embargo_status != Dandiset.EmbargoStatus.OPEN:
            if not self.request.user.is_authenticated:
                # Clients must be authenticated to access it
                raise NotAuthenticated()
            if not self.request.user.has_perm('owner', dandiset):
                # The user does not have ownership permission
                raise PermissionDenied()
        return super().get_queryset()

    @swagger_auto_schema(
        responses={
            200: 'The version metadata.',
        },
        manual_parameters=[DANDISET_PK_PARAM, VERSION_PARAM],
    )
    def retrieve(self, request, **kwargs):
        version = self.get_object()
        return Response(version.metadata, status=status.HTTP_200_OK)

    @swagger_auto_schema(
        manual_parameters=[DANDISET_PK_PARAM, VERSION_PARAM],
        responses={200: VersionDetailSerializer()},
    )
    @action(detail=True, methods=['GET'])
    def info(self, request, **kwargs):
        """Django serialization of a version."""
        version = self.get_object()
        serializer = VersionDetailSerializer(instance=version)
        return Response(serializer.data, status=status.HTTP_200_OK)

    @swagger_auto_schema(
        request_body=VersionMetadataSerializer(),
        responses={200: VersionDetailSerializer()},
        manual_parameters=[DANDISET_PK_PARAM, VERSION_PARAM],
    )
    @method_decorator(
        permission_required_or_403('owner', (Dandiset, 'pk', 'dandiset__pk')))
    def update(self, request, **kwargs):
        """Update the metadata of a version."""
        version: Version = self.get_object()
        if version.version != 'draft':
            return Response(
                'Only draft versions can be modified.',
                status=status.HTTP_405_METHOD_NOT_ALLOWED,
            )

        serializer = VersionMetadataSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        name = serializer.validated_data['name']
        metadata = serializer.validated_data['metadata']
        # Strip away any computed fields
        metadata = Version.strip_metadata(metadata)

        version.name = name
        version.metadata = metadata
        version.status = Version.Status.PENDING
        version.save()

        serializer = VersionDetailSerializer(instance=version)
        return Response(serializer.data, status=status.HTTP_200_OK)

    @swagger_auto_schema(
        request_body=no_body,
        manual_parameters=[DANDISET_PK_PARAM, VERSION_PARAM],
        responses={200: VersionSerializer()},
    )
    @action(detail=True, methods=['POST'])
    @method_decorator(
        permission_required_or_403('owner', (Dandiset, 'pk', 'dandiset__pk')))
    def publish(self, request, **kwargs):
        """Publish a version."""
        old_version: Version = self.get_object()
        if old_version.version != 'draft':
            return Response(
                'Only draft versions can be published',
                status=status.HTTP_405_METHOD_NOT_ALLOWED,
            )
        if (old_version.dandiset.zarr_archives.exists()
                or old_version.dandiset.embargoed_zarr_archives.exists()):
            raise ValidationError(
                'Cannot publish dandisets which contain zarrs')
        if not old_version.valid:
            return Response(
                'Dandiset metadata or asset metadata is not valid',
                status=status.HTTP_400_BAD_REQUEST,
            )

        new_version = old_version.publish_version

        new_version.doi = doi.create_doi(new_version)

        new_version.save()
        # Bulk create the join table rows to optimize linking assets to new_version
        AssetVersions = Version.assets.through  # noqa: N806

        # Add a new many-to-many association directly to any already published assets
        already_published_assets = old_version.assets.filter(published=True)
        AssetVersions.objects.bulk_create([
            AssetVersions(asset_id=asset['id'], version_id=new_version.id)
            for asset in already_published_assets.values('id')
        ])

        # Publish any draft assets
        # Import here to avoid dependency cycle
        from dandiapi.api.models import Asset

        draft_assets = old_version.assets.filter(published=False).all()
        for draft_asset in draft_assets:
            draft_asset.publish()
        Asset.objects.bulk_update(draft_assets, ['metadata', 'published'])

        AssetVersions.objects.bulk_create([
            AssetVersions(asset_id=asset.id, version_id=new_version.id)
            for asset in draft_assets
        ])

        # Save again to recompute metadata, specifically assetsSummary
        new_version.save()

        # Set the version of the draft to PUBLISHED so that it cannot be publishd again without
        # being modified and revalidated
        old_version.status = Version.Status.PUBLISHED
        old_version.save()

        write_manifest_files.delay(new_version.id)

        serializer = VersionSerializer(new_version)
        return Response(serializer.data, status=status.HTTP_200_OK)

    @swagger_auto_schema(
        manual_parameters=[DANDISET_PK_PARAM, VERSION_PARAM], )
    def destroy(self, request, **kwargs):
        """
        Delete a version.

        Deletes a version. Only published versions can be deleted, and only by
        admin users.
        """
        version: Version = self.get_object()
        if version.version == 'draft':
            return Response(
                'Cannot delete draft versions',
                status=status.HTTP_403_FORBIDDEN,
            )
        elif not request.user.is_superuser:
            return Response(
                'Cannot delete published versions',
                status=status.HTTP_403_FORBIDDEN,
            )
        else:
            doi = version.doi
            version.delete()
            if doi is not None:
                delete_doi_task.delay(doi)
            return Response(None, status=status.HTTP_204_NO_CONTENT)
예제 #6
0
class VersionViewSet(NestedViewSetMixin, DetailSerializerMixin,
                     ReadOnlyModelViewSet):
    queryset = Version.objects.all().select_related('dandiset').order_by(
        'created')
    queryset_detail = queryset

    permission_classes = [IsAuthenticatedOrReadOnly]
    serializer_class = VersionSerializer
    serializer_detail_class = VersionDetailSerializer
    pagination_class = DandiPagination

    lookup_field = 'version'
    lookup_value_regex = Version.VERSION_REGEX

    @swagger_auto_schema(
        request_body=VersionMetadataSerializer(),
        responses={200: VersionDetailSerializer()},
    )
    # @permission_required_or_403('owner', (Dandiset, 'pk', 'dandiset__pk'))
    def update(self, request, **kwargs):
        """Update the metadata of a version."""
        version: Version = self.get_object()

        # TODO @permission_required doesn't work on methods
        # https://github.com/django-guardian/django-guardian/issues/723
        response = get_40x_or_None(request, ['owner'],
                                   version.dandiset,
                                   return_403=True)
        if response:
            return response

        serializer = VersionMetadataSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        version_metadata: VersionMetadata
        version_metadata, created = VersionMetadata.objects.get_or_create(
            name=serializer.validated_data['name'],
            metadata=serializer.validated_data['metadata'])

        if created:
            version_metadata.save()

        version.metadata = version_metadata
        version.save()

        serializer = VersionDetailSerializer(instance=version)
        return Response(serializer.data, status=status.HTTP_200_OK)

    @swagger_auto_schema(request_body=no_body,
                         responses={200: VersionSerializer()})
    @action(detail=True, methods=['POST'])
    # @permission_required_or_403('owner', (Dandiset, 'pk', 'dandiset__pk'))
    def publish(self, request, **kwargs):
        old_version = self.get_object()

        # TODO @permission_required doesn't work on methods
        # https://github.com/django-guardian/django-guardian/issues/723
        response = get_40x_or_None(request, ['owner'],
                                   old_version.dandiset,
                                   return_403=True)
        if response:
            return response

        new_version = Version.copy(old_version)

        new_version.doi = doi.create_doi(new_version)

        new_version.save()
        for asset in old_version.assets.all():
            new_version.assets.add(asset)

        new_version.write_yamls()

        serializer = VersionSerializer(new_version)
        return Response(serializer.data, status=status.HTTP_200_OK)
예제 #7
0
class DandisetViewSet(ReadOnlyModelViewSet):
    serializer_class = DandisetDetailSerializer
    pagination_class = DandiPagination
    filter_backends = [filters.SearchFilter, DandisetFilterBackend]
    search_fields = ['versions__metadata']

    lookup_value_regex = Dandiset.IDENTIFIER_REGEX
    # This is to maintain consistency with the auto-generated names shown in swagger.
    lookup_url_kwarg = 'dandiset__pk'

    def get_queryset(self):
        # Only include embargoed dandisets which belong to the current user
        queryset = Dandiset.objects
        if self.action == 'list':
            queryset = Dandiset.objects.visible_to(
                self.request.user).order_by('created')

            query_serializer = DandisetQueryParameterSerializer(
                data=self.request.query_params)
            query_serializer.is_valid(raise_exception=True)

            # TODO: This will filter the dandisets list if there is a query parameter user=me.
            # This is not a great solution but it is needed for the My Dandisets page.
            user_kwarg = query_serializer.validated_data.get('user')
            if user_kwarg == 'me':
                # Replace the original, rather inefficient queryset with a more specific one
                queryset = get_objects_for_user(
                    self.request.user, 'owner', Dandiset,
                    with_superuser=False).order_by('created')

            show_draft: bool = query_serializer.validated_data['draft']
            show_empty: bool = query_serializer.validated_data['empty']
            show_embargoed: bool = query_serializer.validated_data['embargoed']

            if not show_draft:
                # Only include dandisets that have more than one version, i.e. published dandisets.
                queryset = queryset.annotate(
                    version_count=Count('versions')).filter(
                        version_count__gt=1)
            if not show_empty:
                # Only include dandisets that have assets in their most recent version.
                most_recent_version = (Version.objects.filter(
                    dandiset=OuterRef('pk')).order_by('created').annotate(
                        asset_count=Count('assets'))[:1])
                queryset = queryset.annotate(draft_asset_count=Subquery(
                    most_recent_version.values('asset_count')))
                queryset = queryset.filter(draft_asset_count__gt=0)
            if not show_embargoed:
                queryset = queryset.filter(embargo_status='OPEN')
        return queryset

    def get_object(self):
        # Alternative to path converters, which DRF doesn't support
        # https://docs.djangoproject.com/en/3.0/topics/http/urls/#registering-custom-path-converters

        lookup_url = self.kwargs[self.lookup_url_kwarg]
        try:
            lookup_value = int(lookup_url)
        except ValueError:
            raise Http404('Not a valid identifier.')
        self.kwargs[self.lookup_url_kwarg] = lookup_value

        dandiset = super().get_object()
        if dandiset.embargo_status != Dandiset.EmbargoStatus.OPEN:
            if not self.request.user.is_authenticated:
                # Clients must be authenticated to access it
                raise NotAuthenticated()
            if not self.request.user.has_perm('owner', dandiset):
                # The user does not have ownership permission
                raise PermissionDenied()
        return dandiset

    @swagger_auto_schema(
        request_body=VersionMetadataSerializer(),
        query_serializer=CreateDandisetQueryParameterSerializer(),
        responses={200: DandisetDetailSerializer()},
        operation_summary='Create a new dandiset.',
        operation_description='',
    )
    def create(self, request: Request):
        """Create a new dandiset."""
        serializer = VersionMetadataSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        query_serializer = CreateDandisetQueryParameterSerializer(
            data=request.query_params)
        query_serializer.is_valid(raise_exception=True)
        if query_serializer.validated_data['embargo']:
            embargo_status = Dandiset.EmbargoStatus.EMBARGOED
        else:
            embargo_status = Dandiset.EmbargoStatus.OPEN

        name = serializer.validated_data['name']
        metadata = serializer.validated_data['metadata']
        # Strip away any computed fields
        metadata = Version.strip_metadata(metadata)

        # Only inject a schemaVersion and default contributor field if they are
        # not specified in the metadata
        metadata = {
            'schemaKey':
            'Dandiset',
            'schemaVersion':
            settings.DANDI_SCHEMA_VERSION,
            'contributor': [
                {
                    'name':
                    f'{request.user.last_name}, {request.user.first_name}',
                    'email': request.user.email,
                    'roleName': ['dcite:ContactPerson'],
                    'schemaKey': 'Person',
                    'affiliation': [],
                    'includeInCitation': True,
                },
            ],
            # TODO: move this into dandischema
            'access': [{
                'schemaKey':
                'AccessRequirements',
                'status':
                'dandi:OpenAccess' if embargo_status
                == Dandiset.EmbargoStatus.OPEN else 'dandi:EmbargoedAccess',
            }],
            **metadata,
        }
        # Run the metadata through the pydantic model to automatically include any boilerplate
        # like the access or repository fields
        metadata = PydanticDandiset.unvalidated(**metadata).json_dict()

        if 'identifier' in serializer.validated_data['metadata']:
            identifier = serializer.validated_data['metadata']['identifier']
            if identifier and not request.user.is_superuser:
                return Response(
                    'Creating a dandiset for a given identifier '
                    f'({identifier} was provided) is admin only operation.',
                    status=403,
                )
            if identifier.startswith('DANDI:'):
                identifier = identifier[6:]
            try:
                dandiset = Dandiset(id=int(identifier),
                                    embargo_status=embargo_status)
            except ValueError:
                return Response(f'Invalid Identifier {identifier}', status=400)
        else:
            dandiset = Dandiset(embargo_status=embargo_status)
        try:
            # Without force_insert, Django will try to UPDATE an existing dandiset if one exists.
            # We want to throw an error if a dandiset already exists.
            dandiset.save(force_insert=True)
        except IntegrityError as e:
            # https://stackoverflow.com/questions/25368020/django-deduce-duplicate-key-exception-from-integrityerror
            # https://www.postgresql.org/docs/13/errcodes-appendix.html
            # Postgres error code 23505 == unique_violation
            if e.__cause__.pgcode == '23505':
                return Response(
                    f'Dandiset {dandiset.identifier} Already Exists',
                    status=400)
            raise e

        logging.info(
            'Created dandiset %s given request with name=%s and metadata=%s',
            dandiset.identifier,
            name,
            metadata,
        )

        assign_perm('owner', request.user, dandiset)

        # Create new draft version
        version = Version(
            dandiset=dandiset,
            name=name,
            metadata=metadata,
            version='draft',
            status=Version.Status.PENDING,
        )
        version.save()

        serializer = DandisetDetailSerializer(instance=dandiset)
        return Response(serializer.data, status=status.HTTP_200_OK)

    @swagger_auto_schema(
        manual_parameters=[DANDISET_PK_PARAM], )
    @method_decorator(
        permission_required_or_403('owner', (Dandiset, 'pk', 'dandiset__pk')))
    def destroy(self, request, dandiset__pk):
        """
        Delete a dandiset.

        Deletes a dandiset. Only dandisets without published versions are deletable.
        """
        dandiset: Dandiset = self.get_object()

        if dandiset.versions.filter(~Q(version='draft')).exists():
            return Response(
                'Cannot delete dandisets with published versions.',
                status=status.HTTP_403_FORBIDDEN,
            )

        dandiset.delete()
        return Response(None, status=status.HTTP_204_NO_CONTENT)

    @swagger_auto_schema(
        methods=['POST'],
        manual_parameters=[DANDISET_PK_PARAM],
        request_body=no_body,
        responses={
            200: 'Dandiset unembargoing dispatched',
            400: 'Dandiset not embargoed',
        },
        operation_summary='Unembargo a dandiset.',
        operation_description=
        ('Unembargo an embargoed dandiset. Only permitted for owners and admins'
         '. If the embargo status is OPEN or UNEMBARGOING, an HTTP 400 is returned.'
         ),
    )
    @action(methods=['POST'], detail=True)
    @method_decorator(
        permission_required_or_403('owner', (Dandiset, 'pk', 'dandiset__pk')))
    def unembargo(self, request, dandiset__pk):
        dandiset: Dandiset = get_object_or_404(Dandiset, pk=dandiset__pk)

        if dandiset.embargo_status != Dandiset.EmbargoStatus.EMBARGOED:
            return Response('Dandiset not embargoed',
                            status=status.HTTP_400_BAD_REQUEST)

        unembargo_dandiset.delay(dandiset.pk)
        return Response(None, status=status.HTTP_200_OK)

    @swagger_auto_schema(
        method='GET',
        manual_parameters=[DANDISET_PK_PARAM],
        responses={200: UserSerializer(many=True)},
        operation_summary='Get owners of a dandiset.',
        operation_description='',
    )
    @swagger_auto_schema(
        method='PUT',
        manual_parameters=[DANDISET_PK_PARAM],
        request_body=UserSerializer(many=True),
        responses={
            200: UserSerializer(many=True),
            400: 'User not found, or cannot remove all owners',
        },
        operation_summary='Set owners of a dandiset.',
        operation_description=
        'Set the owners of a dandiset. The user performing this action must\
                               be an owner of the dandiset themself.',
    )
    # TODO move these into a viewset
    @action(methods=['GET', 'PUT'], detail=True)
    def users(self, request, dandiset__pk):
        dandiset = self.get_object()
        if request.method == 'PUT':
            # Verify that the user is currently an owner
            response = get_40x_or_None(request, ['owner'],
                                       dandiset,
                                       return_403=True)
            if response:
                return response

            serializer = UserSerializer(data=request.data, many=True)
            serializer.is_valid(raise_exception=True)

            def get_user_or_400(username):
                # SocialAccount uses the generic JSONField instead of the PostGres JSONFIELD,
                # so it is not empowered to do a more powerful query like:
                # SocialAccount.objects.get(extra_data__login=username)
                # We resort to doing this awkward search instead.
                for social_account in SocialAccount.objects.filter(
                        extra_data__icontains=username):
                    if social_account.extra_data['login'] == username:
                        return social_account.user
                else:
                    try:
                        return User.objects.get(username=username)
                    except ObjectDoesNotExist:
                        raise ValidationError(f'User {username} not found')

            owners = [
                get_user_or_400(username=owner['username'])
                for owner in serializer.validated_data
            ]
            if len(owners) < 1:
                raise ValidationError('Cannot remove all draft owners')

            removed_owners, added_owners = dandiset.set_owners(owners)
            dandiset.save()

            send_ownership_change_emails(dandiset, removed_owners,
                                         added_owners)

        owners = []
        for owner in dandiset.owners:
            try:
                owner = SocialAccount.objects.get(user=owner)
                owner_dict = {'username': owner.extra_data['login']}
                if 'name' in owner.extra_data:
                    owner_dict['name'] = owner.extra_data['name']
                owners.append(owner_dict)
            except SocialAccount.DoesNotExist:
                # Just in case some users aren't using social accounts, have a fallback
                owners.append({
                    'username': owner.username,
                    'name': f'{owner.first_name} {owner.last_name}'
                })
        return Response(owners, status=status.HTTP_200_OK)
예제 #8
0
    def create(self, request: Request):
        """Create a new dandiset."""
        serializer = VersionMetadataSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        query_serializer = CreateDandisetQueryParameterSerializer(
            data=request.query_params)
        query_serializer.is_valid(raise_exception=True)
        if query_serializer.validated_data['embargo']:
            embargo_status = Dandiset.EmbargoStatus.EMBARGOED
        else:
            embargo_status = Dandiset.EmbargoStatus.OPEN

        name = serializer.validated_data['name']
        metadata = serializer.validated_data['metadata']
        # Strip away any computed fields
        metadata = Version.strip_metadata(metadata)

        # Only inject a schemaVersion and default contributor field if they are
        # not specified in the metadata
        metadata = {
            'schemaKey':
            'Dandiset',
            'schemaVersion':
            settings.DANDI_SCHEMA_VERSION,
            'contributor': [
                {
                    'name':
                    f'{request.user.last_name}, {request.user.first_name}',
                    'email': request.user.email,
                    'roleName': ['dcite:ContactPerson'],
                    'schemaKey': 'Person',
                    'affiliation': [],
                    'includeInCitation': True,
                },
            ],
            # TODO: move this into dandischema
            'access': [{
                'schemaKey':
                'AccessRequirements',
                'status':
                'dandi:OpenAccess' if embargo_status
                == Dandiset.EmbargoStatus.OPEN else 'dandi:EmbargoedAccess',
            }],
            **metadata,
        }
        # Run the metadata through the pydantic model to automatically include any boilerplate
        # like the access or repository fields
        metadata = PydanticDandiset.unvalidated(**metadata).json_dict()

        if 'identifier' in serializer.validated_data['metadata']:
            identifier = serializer.validated_data['metadata']['identifier']
            if identifier and not request.user.is_superuser:
                return Response(
                    'Creating a dandiset for a given identifier '
                    f'({identifier} was provided) is admin only operation.',
                    status=403,
                )
            if identifier.startswith('DANDI:'):
                identifier = identifier[6:]
            try:
                dandiset = Dandiset(id=int(identifier),
                                    embargo_status=embargo_status)
            except ValueError:
                return Response(f'Invalid Identifier {identifier}', status=400)
        else:
            dandiset = Dandiset(embargo_status=embargo_status)
        try:
            # Without force_insert, Django will try to UPDATE an existing dandiset if one exists.
            # We want to throw an error if a dandiset already exists.
            dandiset.save(force_insert=True)
        except IntegrityError as e:
            # https://stackoverflow.com/questions/25368020/django-deduce-duplicate-key-exception-from-integrityerror
            # https://www.postgresql.org/docs/13/errcodes-appendix.html
            # Postgres error code 23505 == unique_violation
            if e.__cause__.pgcode == '23505':
                return Response(
                    f'Dandiset {dandiset.identifier} Already Exists',
                    status=400)
            raise e

        logging.info(
            'Created dandiset %s given request with name=%s and metadata=%s',
            dandiset.identifier,
            name,
            metadata,
        )

        assign_perm('owner', request.user, dandiset)

        # Create new draft version
        version = Version(
            dandiset=dandiset,
            name=name,
            metadata=metadata,
            version='draft',
            status=Version.Status.PENDING,
        )
        version.save()

        serializer = DandisetDetailSerializer(instance=dandiset)
        return Response(serializer.data, status=status.HTTP_200_OK)