diff --git a/CHANGELOG.md b/CHANGELOG.md index 455f5bac..7df95082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +v1.10.2 + +- Added project to the admin interface +- coordSystem2 is no longer a required field + +v1.10.1 + +- Check to make sure project's owner is not None before returning username + v1.10.0 - Added support for mrmatrix files diff --git a/tilesets/admin.py b/tilesets/admin.py index 4156e11e..186f6767 100644 --- a/tilesets/admin.py +++ b/tilesets/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin from tilesets.models import Tileset from tilesets.models import ViewConf +from tilesets.models import Project # Register your models here. @@ -25,7 +26,16 @@ class ViewConfAdmin(admin.ModelAdmin): 'uuid', 'higlassVersion', ] + +class ProjectConfAdmin(admin.ModelAdmin): + list_display = [ + 'created', + 'uuid', + 'name', + 'description', + ] admin.site.register(Tileset, TilesetAdmin) admin.site.register(ViewConf, ViewConfAdmin) +admin.site.register(Project, ProjectConfAdmin) diff --git a/tilesets/models.py b/tilesets/models.py index aad6f663..52dbbfb8 100644 --- a/tilesets/models.py +++ b/tilesets/models.py @@ -5,6 +5,12 @@ import slugid from django.db import models +from django.db.models.signals import post_delete +from django.dispatch import receiver + + +def decoded_slugid(): + return slugid.nice() class ViewConf(models.Model): @@ -23,44 +29,46 @@ def __str__(self): ''' return "Viewconf [uuid: {}]".format(self.uuid) -def decoded_slugid(): - return slugid.nice().decode('utf-8') class Project(models.Model): created = models.DateTimeField(auto_now_add=True) last_viewed_time = models.DateTimeField(default=django.utils.timezone.now) - owner = models.ForeignKey(dcam.User, on_delete=models.CASCADE, blank=True, null=True) + owner = models.ForeignKey( + dcam.User, on_delete=models.CASCADE, blank=True, null=True) name = models.TextField(unique=True) description = models.TextField(blank=True) - uuid = models.CharField(max_length=100, unique=True, default=decoded_slugid) + uuid = models.CharField(max_length=100, unique=True, + default=decoded_slugid) private = models.BooleanField(default=False) class Meta: ordering = ('created',) permissions = (('read', "Read permission"), - ('write', 'Modify tileset'), - ('admin', 'Administrator priviliges'), - ) + ('write', 'Modify tileset'), + ('admin', 'Administrator priviliges'), + ) def __str__(self): return "Project [name: " + self.name + "]" + class Tileset(models.Model): created = models.DateTimeField(auto_now_add=True) - uuid = models.CharField(max_length=100, unique=True, default=decoded_slugid) + uuid = models.CharField(max_length=100, unique=True, + default=decoded_slugid) # processed_file = models.TextField() datafile = models.FileField(upload_to='uploads') filetype = models.TextField() datatype = models.TextField(default='unknown', blank=True, null=True) project = models.ForeignKey(Project, on_delete=models.CASCADE, - blank=True, null=True) + blank=True, null=True) description = models.TextField(blank=True) coordSystem = models.TextField() - coordSystem2 = models.TextField(default='') + coordSystem2 = models.TextField(default='', blank=True) temporary = models.BooleanField(default=False) owner = models.ForeignKey( @@ -73,13 +81,21 @@ class Tileset(models.Model): class Meta: ordering = ('created',) permissions = (('read', "Read permission"), - ('write', 'Modify tileset'), - ('admin', 'Administrator priviliges'), - ) + ('write', 'Modify tileset'), + ('admin', 'Administrator priviliges'), + ) def __str__(self): ''' Get a string representation of this model. Hopefully useful for the admin interface. ''' - return "Tileset [name: {}] [ft: {}] [uuid: {}]".format(self.name, self.filetype, self.uuid) + return "Tileset [name: {}] [ft: {}] [uuid: {}]".format( + self.name, self.filetype, self.uuid) + + +@receiver(post_delete, sender=Tileset) +def tileset_on_delete(sender, instance, **kwargs): + + if not instance.datafile.name.endswith('..'): + instance.datafile.delete(False) diff --git a/tilesets/serializers.py b/tilesets/serializers.py index a6367e6c..428ecb9b 100644 --- a/tilesets/serializers.py +++ b/tilesets/serializers.py @@ -84,6 +84,9 @@ def retrieve_project_owner(self, obj): if obj.project is None: return '' + if obj.project.owner is None: + return '' + return obj.project.owner.username class Meta: diff --git a/tilesets/tests.py b/tilesets/tests.py index 0a6859cc..af2e33b2 100644 --- a/tilesets/tests.py +++ b/tilesets/tests.py @@ -51,8 +51,8 @@ def media_file_exists(filename): ------- The return value. True for success, False otherwise. ''' - return False if not op.exists(media_file(filename)) else True - + return False if not op.exists(media_file(filename)) else True + def add_file(filename, sub_dir='uploads/data'): ''' @@ -468,8 +468,8 @@ def test_to_string(self): ) upload_file = open('data/dixon2012-h1hesc-hindiii-allreps-filtered.1000kb.multires.cool', 'rb') self.cooler = tm.Tileset.objects.create( - datafile=dcfu.SimpleUploadedFile(upload_file.name, - upload_file.read()), + datafile=dcfu.SimpleUploadedFile( + upload_file.name, upload_file.read()), filetype='cooler', owner=self.user1, uuid='x1x' @@ -478,6 +478,25 @@ def test_to_string(self): cooler_string = str(self.cooler) self.assertTrue(cooler_string.find("name") > 0) + def test_destroy_deletes_file(self): + self.user1 = dcam.User.objects.create_user( + username='user1', password='pass' + ) + upload_file = open('data/dixon2012-h1hesc-hindiii-allreps-filtered.1000kb.multires.cool', 'rb') + ts = tm.Tileset.objects.create( + datafile=dcfu.SimpleUploadedFile( + upload_file.name, upload_file.read()), + filetype='cooler', + owner=self.user1, + uuid='x2x' + ) + filepath = op.join(hss.MEDIA_ROOT, ts.datafile.name) + self.assertTrue(op.exists(filepath)) + + ts.delete() + self.assertFalse(op.exists(filepath)) + + class UnknownTilesetTypeTest(dt.TestCase): def setUp(self): self.user1 = dcam.User.objects.create_user( @@ -643,11 +662,11 @@ def test_permissions(self): assert(response.status_code == 201) ret = json.loads(response.content.decode('utf-8')) - + # update media filename for whatever name the server ended up using (i.e., in case of duplicates, a random suffix is added) assert('datafile' in ret) fname = op.basename(ret['datafile']) - + # test that said media file exists assert(media_file_exists(fname)) @@ -657,10 +676,10 @@ def test_permissions(self): # user2 should not be able to delete the tileset created by user1 resp = c2.delete('/api/v1/tilesets/' + ret['uuid'] + "/") assert(resp.status_code == 403) - + # the media file should still exist assert(media_file_exists(fname)) - + # user2 should not be able to rename the tileset created by user1 resp = c2.put('/api/v1/tilesets/' + ret['uuid'] + "/", data='{"name":"newname"}', content_type='application/json') @@ -670,7 +689,7 @@ def test_permissions(self): resp = c1.get("/api/v1/tilesets/") assert(json.loads(resp.content.decode('utf-8'))['count'] == 1) assert(media_file_exists(fname)) - + # user1 should be able to rename or modify their tileset resp = c1.patch('/api/v1/tilesets/' + ret['uuid'] + "/", data='{"name":"newname"}', content_type='application/json') @@ -679,7 +698,7 @@ def test_permissions(self): # apply GET on uuid to ensure that tileset has the newly modified name resp = c1.get("/api/v1/tilesets/" + ret['uuid'] + '/') assert(json.loads(resp.content.decode('utf-8'))['name'] == 'newname') - + # the media file should still exist with the same name assert(media_file_exists(fname)) diff --git a/tilesets/views.py b/tilesets/views.py index 34025c0b..cf70687d 100644 --- a/tilesets/views.py +++ b/tilesets/views.py @@ -758,12 +758,14 @@ def destroy(self, request, *args, **kwargs): return JsonResponse({'error': 'The uuid parameter is undefined'}, status=400) try: instance = self.get_object() - self.perform_destroy(instance) - filename = instance.datafile.name - filepath = op.join(hss.MEDIA_ROOT, filename) + + filepath = op.join(hss.MEDIA_ROOT, instance.datafile.name) if not op.isfile(filepath): return JsonResponse({'error': 'Unable to locate tileset media file for deletion: {}'.format(filepath)}, status=500) - os.remove(filepath) + + # model's post-destroy handler does the actual file deletion + self.perform_destroy(instance) + except dh.Http404: return JsonResponse({'error': 'Unable to locate tileset instance for uuid: {}'.format(uuid)}, status=404) except dbm.ProtectedError as dbpe: