Skip to content

Commit

Permalink
Add deleted_at Field to Attachments.
Browse files Browse the repository at this point in the history
Create soft_delete action that will populate deleted_at field for attachments. Fixes #1696
Call soft_delete action when replacing attachments
  • Loading branch information
WinnyTroy committed Feb 6, 2020
1 parent 757014e commit 9668dc0
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 2 deletions.
65 changes: 65 additions & 0 deletions onadata/apps/api/tests/viewsets/test_attachment_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,71 @@ def test_list_view(self):
self.assertTrue(isinstance(response.data, list))
self.assertEqual(len(response.data), 0)

def test_list_view_surfaces_current_attachment(self):
self._submit_transport_instance_w_attachment()
filename = "1335783522564.JPG"
path = os.path.join(self.main_directory, 'fixtures', 'transportation',
'instances', self.surveys[0], filename)
media_file = django_file(path, 'video2', 'image/jpeg')

Attachment.objects.create(
instance=self.xform.instances.first(),
mimetype='video/mp4',
extension='MP4',
name=filename,
media_file=media_file)

Attachment.objects.create(
instance=self.xform.instances.first(),
mimetype='application/pdf',
extension='PDF',
name=filename,
media_file=media_file)
Attachment.objects.create(
instance=self.xform.instances.first(),
mimetype='text/plain',
extension='TXT',
name=filename,
media_file=media_file)
Attachment.objects.create(
instance=self.xform.instances.first(),
mimetype='audio/mp3',
extension='MP3',
name=filename,
media_file=media_file)

request = self.factory.get('/', **self.extra)
response = self.list_view(request)
self.assertNotEqual(response.get('Cache-Control'), None)
self.assertEqual(response.status_code, 200)
self.assertTrue(isinstance(response.data, list))
self.assertEqual(len(response.data), 5)

# test when the attachment is soft deleted
self.attachment.soft_delete(user=self.user)

request = self.factory.get('/', **self.extra)
response = self.list_view(request)

self.assertEqual(len(response.data), 4)

def test_soft_delete_action_returns_correct_user(self):
self._submit_transport_instance_w_attachment()

request = self.factory.get('/', **self.extra)
response = self.list_view(request)
self.assertEqual(len(response.data), 1)

# test when the attachment is soft deleted
self.attachment.soft_delete(user=self.user)

# Test that deleted_by field captures the right user
self.assertTrue(self.attachment.deleted_by, self.user)

request = self.factory.get('/', **self.extra)
response = self.list_view(request)
self.assertEqual(len(response.data), 0)

def test_data_list_with_xform_in_delete_async(self):
self._submit_transport_instance_w_attachment()

Expand Down
3 changes: 2 additions & 1 deletion onadata/apps/api/viewsets/attachment_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ class AttachmentViewSet(AuthenticateHeaderMixin, CacheControlMixin, ETagsMixin,
content_negotiation_class = MediaFileContentNegotiation
filter_backends = (filters.AttachmentFilter, filters.AttachmentTypeFilter)
lookup_field = 'pk'
queryset = Attachment.objects.filter(instance__deleted_at__isnull=True)
queryset = Attachment.objects.filter(
instance__deleted_at__isnull=True, deleted_at__isnull=True)
permission_classes = (AttachmentObjectPermissions,)
serializer_class = AttachmentSerializer
pagination_class = StandardPageNumberPagination
Expand Down
10 changes: 10 additions & 0 deletions onadata/apps/logger/models/attachment.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from hashlib import md5

from django.db import models
from django.utils import timezone


def get_original_filename(filename):
Expand Down Expand Up @@ -86,3 +87,12 @@ def file_hash(self):
def filename(self):
if self.media_file:
return os.path.basename(self.media_file.name)

def soft_delete(self, user=None):
"""
Soft deletes an attachment by adding a deleted_at timestamp.
"""
self.deleted_at = timezone.now()
if user is not None:
self.deleted_by = user
self.save()
5 changes: 4 additions & 1 deletion onadata/apps/logger/models/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def get_attachment_url(attachment, suffix=None):

def _get_attachments_from_instance(instance):
attachments = []
for a in instance.attachments.all():
for a in instance.attachments.filter(deleted_at__isnull=True):
attachment = dict()
attachment['download_url'] = get_attachment_url(a)
attachment['small_download_url'] = get_attachment_url(a, 'small')
Expand All @@ -79,6 +79,9 @@ def _get_attachments_from_instance(instance):
attachment['id'] = a.id
attachments.append(attachment)

if isinstance(a.deleted_at, datetime):
attachment['deleted_at'] = a.deleted_at.strftime(MONGO_STRFTIME)

return attachments


Expand Down
6 changes: 6 additions & 0 deletions onadata/libs/utils/logger_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ def save_attachments(xform, instance, media_files):
"""
# upload_path = os.path.join(instance.xform.user.username, 'attachments')

filenames = []
for f in media_files:
filename, extension = os.path.splitext(f.name)
extension = extension.replace('.', '')
Expand All @@ -229,6 +230,7 @@ def save_attachments(xform, instance, media_files):
xform.instances_with_osm = True
xform.save()
filename = os.path.basename(f.name)
filenames.append(filename)
media_in_submission = (
filename in instance.get_expected_media() or
[instance.xml.decode('utf-8').find(filename) != -1 if
Expand All @@ -241,6 +243,10 @@ def save_attachments(xform, instance, media_files):
mimetype=content_type,
name=filename,
extension=extension)
Attachment.objects.filter(instance=instance).filter(
~Q(name__in=instance.get_expected_media())).update(
deleted_at=timezone.now())

update_attachment_tracking(instance)


Expand Down

0 comments on commit 9668dc0

Please sign in to comment.