Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delete backing media file on removal of a tileset #102

Open
wants to merge 24 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e267a6b
Switch to settings based limitations
flekschas Oct 31, 2018
ad5c143
If zoom-level == -1 use most efficient zoom level to pull out snippet
flekschas Oct 31, 2018
a257f3a
Update
flekschas Oct 31, 2018
19ffbd7
Adjust to Pete's comments
flekschas Oct 31, 2018
5b5c1a6
Merge pull request #76 from higlass/flekschas/limit-snippet-size
pkerpedjiev Nov 25, 2018
67975e8
Merge branch 'master' of https://github.com/hms-dbmi/higlass-server
pkerpedjiev Nov 30, 2018
5563d6f
Retrieve project name in TilesetSerializer
pkerpedjiev Dec 1, 2018
baba6bc
Bumped django version
pkerpedjiev Jan 8, 2019
9668c90
Bumped clodius version number
pkerpedjiev Jan 19, 2019
9ce4abe
Merged with develop branch
pkerpedjiev Jan 19, 2019
aae628f
Merged with remote develop
pkerpedjiev Feb 5, 2019
c594ee5
Added a slash to the end of the static url
pkerpedjiev Feb 5, 2019
9520547
Updated the CHANGELOG
pkerpedjiev Feb 5, 2019
7a0828d
Bumped the clodius version
pkerpedjiev Feb 11, 2019
d47799e
Merge branch 'develop' of https://github.com/higlass/higlass-server
pkerpedjiev Feb 12, 2019
174dff0
Updated CHANGELOG
pkerpedjiev Mar 13, 2019
8c7248a
Bumped Dockerfile miniconda version
pkerpedjiev Mar 13, 2019
03cb234
Unpinned gcc
pkerpedjiev Mar 13, 2019
d5b5658
Unpinned all packages in Dockerfile
pkerpedjiev Mar 13, 2019
055bca8
Merge branch 'develop' of https://github.com/higlass/higlass-server
pkerpedjiev Mar 13, 2019
bc8c581
Check to make sure project's owner is not none before returning username
pkerpedjiev Mar 13, 2019
5a34912
Added project to admin interface and removed coordSystem2 as a requir…
pkerpedjiev Mar 16, 2019
82cc2b2
Delete backing media file on removal of a tileset
nvictus May 7, 2019
1d3a7cd
Add test for Tileset model post-delete hook
nvictus May 7, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
10 changes: 10 additions & 0 deletions tilesets/admin.py
Original file line number Diff line number Diff line change
@@ -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.


Expand All @@ -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)
44 changes: 30 additions & 14 deletions tilesets/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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(
Expand All @@ -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)
3 changes: 3 additions & 0 deletions tilesets/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
39 changes: 29 additions & 10 deletions tilesets/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'):
'''
Expand Down Expand Up @@ -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'
Expand All @@ -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(
Expand Down Expand Up @@ -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))

Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -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))

Expand Down
10 changes: 6 additions & 4 deletions tilesets/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down