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)
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)
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)
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)
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)
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)
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)
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)