diff --git a/exact/exact/datasets/views.py b/exact/exact/datasets/views.py index 58ff5932..4c74b94c 100644 --- a/exact/exact/datasets/views.py +++ b/exact/exact/datasets/views.py @@ -18,7 +18,7 @@ from exact.administration.models import Product from exact.annotations.models import Annotation, AnnotationType from exact.datasets.forms import DatasetForm, MITOS_WSI_CMCDatasetForm, CATCH_DatasetForm -from exact.images.models import ImageSet, Image +from exact.images.models import AuxiliaryFile, ImageSet, Image from exact.images.views import view_imageset diff --git a/exact/exact/images/admin.py b/exact/exact/images/admin.py index 53cbda5f..1907fcf0 100644 --- a/exact/exact/images/admin.py +++ b/exact/exact/images/admin.py @@ -1,8 +1,9 @@ from django.contrib import admin # Register your models here. -from .models import Image, ImageSet, SetTag +from .models import Image, ImageSet, SetTag, AuxiliaryFile admin.site.register(ImageSet) admin.site.register(Image) admin.site.register(SetTag) +admin.site.register(AuxiliaryFile) diff --git a/exact/exact/images/api_views.py b/exact/exact/images/api_views.py index 3247443e..1b4825b4 100644 --- a/exact/exact/images/api_views.py +++ b/exact/exact/images/api_views.py @@ -114,6 +114,10 @@ def filter_annotation_type(self, queryset, field_name, value): return queryset +class AuxiliaryFileViewSet(viewsets.ModelViewSet): + permission_classes = [permissions.DjangoModelPermissions] + serializer_class = serializers.AuxiliaryFileSerializer + class ImageViewSet(viewsets.ModelViewSet): permission_classes = [permissions.DjangoModelPermissions] @@ -458,13 +462,22 @@ def create(self, request): path = Path(path) name = path.name - image = models.Image( - name=name, - image_set=imageset, - checksum=fchecksum) + if (Path(path).suffix.lower().endswith(".csv") or Path(path).suffix.lower().endswith(".txt") or + Path(path).suffix.lower().endswith(".json") or Path(path).suffix.lower().endswith(".sqlite")): + # This is an auxiliary file, not an image. + newFile = models.AuxiliaryFile(image_set=imageset, name=name, filesize=os.path.getsize(path)) + images.append(newFile) + + else: + + + image = models.Image( + name=name, + image_set=imageset, + checksum=fchecksum) - image.save_file(path) - images.append(image) + image.save_file(path) + images.append(image) except Exception as e: errors.append(e.message) diff --git a/exact/exact/images/migrations/0035_auxiliaryfile.py b/exact/exact/images/migrations/0035_auxiliaryfile.py new file mode 100644 index 00000000..3725259d --- /dev/null +++ b/exact/exact/images/migrations/0035_auxiliaryfile.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.18 on 2025-01-21 09:20 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('images', '0034_alter_framedescription_description_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='AuxiliaryFile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=256)), + ('filesize', models.IntegerField()), + ('location', models.CharField(blank=True, max_length=256, null=True)), + ('description', models.TextField(blank=True, max_length=1000, null=True)), + ('time', models.DateTimeField(auto_now_add=True)), + ('associated_to_image', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='images.image')), + ('creator', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ('image_set', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='auxiliary_files', to='images.imageset')), + ], + ), + ] diff --git a/exact/exact/images/models.py b/exact/exact/images/models.py index 8da279f3..f338be43 100644 --- a/exact/exact/images/models.py +++ b/exact/exact/images/models.py @@ -367,10 +367,10 @@ def save_file(self, path:Path): raise def __str__(self): - return u'Image: {0}'.format(self.name) + return u'Image: {0} (Image Set: {1}, Team: {2})'.format(self.name, self.image_set.name, self.image_set.team.name) def __repr__(self): - return u'Image: {0}'.format(self.name) + return u'Image: {0} (Image Set: {1}, Team: {2})'.format(self.name, self.image_set.name, self.image_set.team.name) # Image signals for del the cache @receiver([post_save, post_delete, m2m_changed], sender=Image) @@ -380,6 +380,40 @@ def image_changed_handler(sender, instance, **kwargs): if hasattr(cache, "delete_pattern"): cache.delete_pattern(f"*/api/v1/images/image_sets/{instance.image_set_id}/*") +class AuxiliaryFile(models.Model): + name = models.CharField(max_length=256) + filesize = models.IntegerField() + location = models.CharField(max_length=256, null=True, blank=True) + description = models.TextField(max_length=1000, null=True, blank=True) + associated_to_image = models.ForeignKey(Image, + on_delete=models.SET_NULL, + null=True) + time = models.DateTimeField(auto_now_add=True) + image_set = models.ForeignKey( + 'ImageSet', on_delete=models.CASCADE, related_name='auxiliary_files') + creator = models.ForeignKey(settings.AUTH_USER_MODEL, + default=None, + on_delete=models.SET_NULL, + null=True, + blank=True) + + + def path(self): + return os.path.join(self.image_set.root_path(), self.name) + + def delete(self, *args, **kwargs): + #self.image_set.zip_state = ImageSet.ZipState.INVALID + #self.image_set.save(update_fields=('zip_state',)) + file_path = Path(settings.IMAGE_PATH) / self.path() + if os.path.exists(file_path): + os.remove(file_path) + super(AuxiliaryFile, self).delete(*args, **kwargs) + + def __str__(self): + return '%s (ImageSet: %s, Team: %s) ' % (self.name, self.image_set.name, self.image_set.team.name) + + + class ImageSet(models.Model): class Meta: unique_together = [ @@ -537,7 +571,9 @@ def has_perm(self, permission: str, user: get_user_model()) -> bool: return permission in self.get_perms(user) def __str__(self): - return u'Imageset: {0}'.format(self.name) + return u'Imageset: {0} (Team: {1})'.format(self.name, self.team.name) + + @property def prio_symbol(self): diff --git a/exact/exact/images/serializers.py b/exact/exact/images/serializers.py index 00588421..b7a2d661 100644 --- a/exact/exact/images/serializers.py +++ b/exact/exact/images/serializers.py @@ -35,6 +35,28 @@ class Meta: "FrameDescriptions" : ('exact.images.serializers.FrameDescriptionSerializer', {'read_only': True, 'many':True}), } +class AuxiliaryFileSerializer(FlexFieldsModelSerializer): + class Meta: + model = Image + fields = ( + 'id', + 'name', + 'filename', + 'description', + 'creator', + 'associated_to_image', + 'time', + 'filesize', + 'image_set', + ) + + expandable_fields = { + "image_set": ('exact.images.serializers.ImageSetSerializer', {'read_only': True}), + "associated_to_image": ('exact.images.serializers.ImageSerializer', {'read_only': True}), + } + + + class SetTagSerializer(FlexFieldsModelSerializer): class Meta: diff --git a/exact/exact/images/templates/images/imageset.html b/exact/exact/images/templates/images/imageset.html index 6f89d08d..0fff49d5 100644 --- a/exact/exact/images/templates/images/imageset.html +++ b/exact/exact/images/templates/images/imageset.html @@ -235,6 +235,24 @@