diff --git a/ami/exports/views.py b/ami/exports/views.py index 0c8cc9a16..65e1ae030 100644 --- a/ami/exports/views.py +++ b/ami/exports/views.py @@ -1,6 +1,7 @@ from rest_framework import status from rest_framework.response import Response +from ami.base.permissions import ObjectPermission from ami.base.views import ProjectMixin from ami.exports.serializers import DataExportSerializer from ami.jobs.models import DataExportJob, Job, SourceImageCollection @@ -16,6 +17,7 @@ class ExportViewSet(DefaultViewSet, ProjectMixin): queryset = DataExport.objects.all() serializer_class = DataExportSerializer + permission_classes = [ObjectPermission] ordering_fields = ["id", "format", "file_size", "created_at", "updated_at"] def get_queryset(self): @@ -56,13 +58,19 @@ def create(self, request, *args, **kwargs): status=status.HTTP_400_BAD_REQUEST, ) - # Create DataExport object - data_export = DataExport.objects.create( + # Create unsaved DataExport instance + data_export = DataExport( user=request.user, format=format_type, filters=filters, project=project, ) + + # Check permissions on the unsaved instance + self.check_object_permissions(request, data_export) + + # Save the instance after permission check passes + data_export.save() data_export.update_record_count() job_name = f"Export occurrences{f' for collection {collection.pk}' if collection else ''}" diff --git a/ami/main/migrations/0079_alter_project_options.py b/ami/main/migrations/0079_alter_project_options.py new file mode 100644 index 000000000..8cfcd5ada --- /dev/null +++ b/ami/main/migrations/0079_alter_project_options.py @@ -0,0 +1,61 @@ +# Generated by Django 4.2.10 on 2025-12-18 12:16 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("main", "0078_classification_applied_to"), + ] + + operations = [ + migrations.AlterModelOptions( + name="project", + options={ + "ordering": ["-priority", "created_at"], + "permissions": [ + ("create_identification", "Can create identifications"), + ("update_identification", "Can update identifications"), + ("delete_identification", "Can delete identifications"), + ("create_job", "Can create a job"), + ("update_job", "Can update a job"), + ("run_ml_job", "Can run/retry/cancel ML jobs"), + ("run_populate_captures_collection_job", "Can run/retry/cancel Populate Collection jobs"), + ("run_data_storage_sync_job", "Can run/retry/cancel Data Storage Sync jobs"), + ("run_data_export_job", "Can run/retry/cancel Data Export jobs"), + ("run_single_image_ml_job", "Can process a single capture"), + ("run_post_processing_job", "Can run/retry/cancel Post-Processing jobs"), + ("delete_job", "Can delete a job"), + ("create_deployment", "Can create a deployment"), + ("delete_deployment", "Can delete a deployment"), + ("update_deployment", "Can update a deployment"), + ("sync_deployment", "Can sync images to a deployment"), + ("create_sourceimagecollection", "Can create a collection"), + ("update_sourceimagecollection", "Can update a collection"), + ("delete_sourceimagecollection", "Can delete a collection"), + ("populate_sourceimagecollection", "Can populate a collection"), + ("create_sourceimage", "Can create a source image"), + ("update_sourceimage", "Can update a source image"), + ("delete_sourceimage", "Can delete a source image"), + ("star_sourceimage", "Can star a source image"), + ("create_sourceimageupload", "Can create a source image upload"), + ("update_sourceimageupload", "Can update a source image upload"), + ("delete_sourceimageupload", "Can delete a source image upload"), + ("create_s3storagesource", "Can create storage"), + ("delete_s3storagesource", "Can delete storage"), + ("update_s3storagesource", "Can update storage"), + ("test_s3storagesource", "Can test storage connection"), + ("create_site", "Can create a site"), + ("delete_site", "Can delete a site"), + ("update_site", "Can update a site"), + ("create_device", "Can create a device"), + ("delete_device", "Can delete a device"), + ("update_device", "Can update a device"), + ("create_dataexport", "Can create a data export"), + ("update_dataexport", "Can update a data export"), + ("delete_dataexport", "Can delete a data export"), + ("view_private_data", "Can view private data"), + ], + }, + ), + ] diff --git a/ami/main/models.py b/ami/main/models.py index f672c2832..edf26ce93 100644 --- a/ami/main/models.py +++ b/ami/main/models.py @@ -404,9 +404,13 @@ class Permissions: DELETE_DEVICE = "delete_device" UPDATE_DEVICE = "update_device" + # Data Export permissions + CREATE_DATA_EXPORT = "create_dataexport" + UPDATE_DATA_EXPORT = "update_dataexport" + DELETE_DATA_EXPORT = "delete_dataexport" + # Other permissions VIEW_PRIVATE_DATA = "view_private_data" - TRIGGER_EXPORT = "trigger_export" DELETE_OCCURRENCES = "delete_occurrences" IMPORT_DATA = "import_data" MANAGE_MEMBERS = "manage_members" @@ -460,9 +464,12 @@ class Meta: ("create_device", "Can create a device"), ("delete_device", "Can delete a device"), ("update_device", "Can update a device"), + # Data Export permissions + ("create_dataexport", "Can create a data export"), + ("update_dataexport", "Can update a data export"), + ("delete_dataexport", "Can delete a data export"), # Other permissions ("view_private_data", "Can view private data"), - ("trigger_exports", "Can trigger data exports"), ] diff --git a/ami/users/roles.py b/ami/users/roles.py index a913198fc..4e71975d1 100644 --- a/ami/users/roles.py +++ b/ami/users/roles.py @@ -62,7 +62,11 @@ class BasicMember(Role): class Researcher(Role): - permissions = BasicMember.permissions | {Project.Permissions.TRIGGER_EXPORT} + permissions = BasicMember.permissions | { + Project.Permissions.CREATE_DATA_EXPORT, + Project.Permissions.UPDATE_DATA_EXPORT, + Project.Permissions.DELETE_DATA_EXPORT, + } class Identifier(Role):