Add document trashed event
Closes GitLab issue #608. Thanks to Vikas Kedia (@vikaskedia) for the report. Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
This commit is contained in:
@@ -131,6 +131,8 @@
|
|||||||
- Add the document template sandbox feature.
|
- Add the document template sandbox feature.
|
||||||
- Use the generic TemplateField for the expression field
|
- Use the generic TemplateField for the expression field
|
||||||
of index tree templates.
|
of index tree templates.
|
||||||
|
- Add document trashed event. Closes GitLab issue #608
|
||||||
|
Thanks to Vikas Kedia (@vikaskedia) for the report.
|
||||||
|
|
||||||
3.2.10 (2019-XX-XX)
|
3.2.10 (2019-XX-XX)
|
||||||
===================
|
===================
|
||||||
|
|||||||
@@ -216,6 +216,7 @@ Bugs fixed or issues closed
|
|||||||
- :gitlab-issue:`532` Workflow preview isn't updated right after transitions are modified
|
- :gitlab-issue:`532` Workflow preview isn't updated right after transitions are modified
|
||||||
- :gitlab-issue:`540` hint-outdated/update documentation
|
- :gitlab-issue:`540` hint-outdated/update documentation
|
||||||
- :gitlab-issue:`594` 3.2b1: Unable to install/run under Python 3.5/3.6/3.7
|
- :gitlab-issue:`594` 3.2b1: Unable to install/run under Python 3.5/3.6/3.7
|
||||||
|
- :gitlab-issue:`608` How to know who put a document in trash can? [Video]
|
||||||
- :gitlab-issue:`634` Failing docker entrypoint when using secret config
|
- :gitlab-issue:`634` Failing docker entrypoint when using secret config
|
||||||
- :gitlab-issue:`635` Build a docker image for Python3
|
- :gitlab-issue:`635` Build a docker image for Python3
|
||||||
- :gitlab-issue:`640` UX: "Toast" Popup position prevents access to actions
|
- :gitlab-issue:`640` UX: "Toast" Popup position prevents access to actions
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ event_document_new_version = namespace.add_event_type(
|
|||||||
event_document_properties_edit = namespace.add_event_type(
|
event_document_properties_edit = namespace.add_event_type(
|
||||||
label=_('Document properties edited'), name='document_edit'
|
label=_('Document properties edited'), name='document_edit'
|
||||||
)
|
)
|
||||||
|
event_document_trashed = namespace.add_event_type(
|
||||||
|
label=_('Document trashed'), name='document_trashed'
|
||||||
|
)
|
||||||
# The type of an existing document is changed to another type
|
# The type of an existing document is changed to another type
|
||||||
event_document_type_change = namespace.add_event_type(
|
event_document_type_change = namespace.add_event_type(
|
||||||
label=_('Document type changed'), name='document_type_change'
|
label=_('Document type changed'), name='document_type_change'
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import uuid
|
|||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
from django.db import models
|
from django.db import models, transaction
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
@@ -13,7 +13,7 @@ from django.utils.translation import ugettext, ugettext_lazy as _
|
|||||||
|
|
||||||
from ..events import (
|
from ..events import (
|
||||||
event_document_create, event_document_properties_edit,
|
event_document_create, event_document_properties_edit,
|
||||||
event_document_type_change,
|
event_document_trashed, event_document_type_change,
|
||||||
)
|
)
|
||||||
from ..managers import DocumentManager, PassthroughManager, TrashCanManager
|
from ..managers import DocumentManager, PassthroughManager, TrashCanManager
|
||||||
from ..settings import setting_language
|
from ..settings import setting_language
|
||||||
@@ -112,11 +112,14 @@ class Document(models.Model):
|
|||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
to_trash = kwargs.pop('to_trash', True)
|
to_trash = kwargs.pop('to_trash', True)
|
||||||
|
_user = kwargs.pop('_user', True)
|
||||||
|
|
||||||
if not self.in_trash and to_trash:
|
if not self.in_trash and to_trash:
|
||||||
self.in_trash = True
|
self.in_trash = True
|
||||||
self.deleted_date_time = now()
|
self.deleted_date_time = now()
|
||||||
self.save()
|
with transaction.atomic():
|
||||||
|
self.save(_commit_events=False)
|
||||||
|
event_document_trashed.commit(actor=_user, target=self)
|
||||||
else:
|
else:
|
||||||
for version in self.versions.all():
|
for version in self.versions.all():
|
||||||
version.delete()
|
version.delete()
|
||||||
|
|||||||
@@ -268,3 +268,50 @@ class DocumentViewTestMixin(object):
|
|||||||
|
|
||||||
def _request_empty_trash_view(self):
|
def _request_empty_trash_view(self):
|
||||||
return self.post(viewname='documents:trash_can_empty')
|
return self.post(viewname='documents:trash_can_empty')
|
||||||
|
|
||||||
|
|
||||||
|
class TrashedDocumentViewTestMixin(object):
|
||||||
|
def _request_document_trash_get_view(self):
|
||||||
|
return self.get(
|
||||||
|
viewname='documents:document_trash', kwargs={
|
||||||
|
'pk': self.test_document.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_document_trash_post_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='documents:document_trash', kwargs={
|
||||||
|
'pk': self.test_document.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_trashed_document_restore_get_view(self):
|
||||||
|
return self.get(
|
||||||
|
viewname='documents:document_restore', kwargs={
|
||||||
|
'pk': self.test_document.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_trashed_document_restore_post_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='documents:document_restore', kwargs={
|
||||||
|
'pk': self.test_document.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_trashed_document_delete_get_view(self):
|
||||||
|
return self.get(
|
||||||
|
viewname='documents:document_delete', kwargs={
|
||||||
|
'pk': self.test_document.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_trashed_document_delete_post_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='documents:document_delete', kwargs={
|
||||||
|
'pk': self.test_document.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_trashed_document_list_view(self):
|
||||||
|
return self.get(viewname='documents:document_list_deleted')
|
||||||
|
|||||||
@@ -3,13 +3,16 @@ from __future__ import unicode_literals
|
|||||||
from actstream.models import Action
|
from actstream.models import Action
|
||||||
from django_downloadview import assert_download_response
|
from django_downloadview import assert_download_response
|
||||||
|
|
||||||
from ..events import event_document_download, event_document_view
|
from ..events import (
|
||||||
|
event_document_download, event_document_trashed, event_document_view
|
||||||
|
)
|
||||||
from ..permissions import (
|
from ..permissions import (
|
||||||
permission_document_download, permission_document_view
|
permission_document_download, permission_document_trash,
|
||||||
|
permission_document_view
|
||||||
)
|
)
|
||||||
|
|
||||||
from .base import GenericDocumentViewTestCase
|
from .base import GenericDocumentViewTestCase
|
||||||
|
from .mixins import TrashedDocumentViewTestMixin
|
||||||
|
|
||||||
TEST_DOCUMENT_TYPE_EDITED_LABEL = 'test document type edited label'
|
TEST_DOCUMENT_TYPE_EDITED_LABEL = 'test document type edited label'
|
||||||
TEST_DOCUMENT_TYPE_2_LABEL = 'test document type 2 label'
|
TEST_DOCUMENT_TYPE_2_LABEL = 'test document type 2 label'
|
||||||
@@ -32,21 +35,22 @@ class DocumentEventsTestMixin(object):
|
|||||||
|
|
||||||
|
|
||||||
class DocumentEventsTestCase(
|
class DocumentEventsTestCase(
|
||||||
DocumentEventsTestMixin, GenericDocumentViewTestCase
|
DocumentEventsTestMixin, TrashedDocumentViewTestMixin,
|
||||||
|
GenericDocumentViewTestCase
|
||||||
):
|
):
|
||||||
def test_document_download_event_no_permissions(self):
|
def setUp(self):
|
||||||
|
super(DocumentEventsTestCase, self).setUp()
|
||||||
Action.objects.all().delete()
|
Action.objects.all().delete()
|
||||||
|
|
||||||
|
def test_document_download_event_no_permission(self):
|
||||||
response = self._request_test_document_download_view()
|
response = self._request_test_document_download_view()
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
self.assertEqual(list(Action.objects.any(obj=self.test_document)), [])
|
self.assertEqual(list(Action.objects.any(obj=self.test_document)), [])
|
||||||
|
|
||||||
def test_document_download_event_with_permissions(self):
|
def test_document_download_event_with_access(self):
|
||||||
self.expected_content_types = ('image/png; charset=utf-8',)
|
self.expected_content_types = ('image/png; charset=utf-8',)
|
||||||
|
|
||||||
Action.objects.all().delete()
|
|
||||||
|
|
||||||
self.grant_access(
|
self.grant_access(
|
||||||
obj=self.test_document, permission=permission_document_download
|
obj=self.test_document, permission=permission_document_download
|
||||||
)
|
)
|
||||||
@@ -66,17 +70,13 @@ class DocumentEventsTestCase(
|
|||||||
self.assertEqual(event.target, self.test_document)
|
self.assertEqual(event.target, self.test_document)
|
||||||
self.assertEqual(event.verb, event_document_download.id)
|
self.assertEqual(event.verb, event_document_download.id)
|
||||||
|
|
||||||
def test_document_view_event_no_permissions(self):
|
def test_document_view_event_no_permission(self):
|
||||||
Action.objects.all().delete()
|
|
||||||
|
|
||||||
response = self._request_test_document_preview_view()
|
response = self._request_test_document_preview_view()
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
self.assertEqual(list(Action.objects.any(obj=self.test_document)), [])
|
self.assertEqual(list(Action.objects.any(obj=self.test_document)), [])
|
||||||
|
|
||||||
def test_document_view_event_with_permissions(self):
|
def test_document_view_event_with_access(self):
|
||||||
Action.objects.all().delete()
|
|
||||||
|
|
||||||
self.grant_access(
|
self.grant_access(
|
||||||
obj=self.test_document, permission=permission_document_view
|
obj=self.test_document, permission=permission_document_view
|
||||||
)
|
)
|
||||||
@@ -88,3 +88,22 @@ class DocumentEventsTestCase(
|
|||||||
self.assertEqual(event.actor, self._test_case_user)
|
self.assertEqual(event.actor, self._test_case_user)
|
||||||
self.assertEqual(event.target, self.test_document)
|
self.assertEqual(event.target, self.test_document)
|
||||||
self.assertEqual(event.verb, event_document_view.id)
|
self.assertEqual(event.verb, event_document_view.id)
|
||||||
|
|
||||||
|
def test_document_trashed_view_event_no_permission(self):
|
||||||
|
response = self._request_document_trash_post_view()
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
self.assertEqual(list(Action.objects.any(obj=self.test_document)), [])
|
||||||
|
|
||||||
|
def test_document_trashed_view_event_with_access(self):
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document, permission=permission_document_trash
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_document_trash_post_view()
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
event = Action.objects.any(obj=self.test_document).first()
|
||||||
|
self.assertEqual(event.actor, self._test_case_user)
|
||||||
|
self.assertEqual(event.target, self.test_document)
|
||||||
|
self.assertEqual(event.verb, event_document_trashed.id)
|
||||||
|
|||||||
@@ -7,57 +7,11 @@ from ..permissions import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from .base import GenericDocumentViewTestCase
|
from .base import GenericDocumentViewTestCase
|
||||||
|
from .mixins import TrashedDocumentViewTestMixin
|
||||||
|
|
||||||
|
|
||||||
class TrashedDocumentTestMixin(object):
|
class TrashedDocumentViewTestCase(
|
||||||
def _request_document_trash_get_view(self):
|
TrashedDocumentViewTestMixin, GenericDocumentViewTestCase
|
||||||
return self.get(
|
|
||||||
viewname='documents:document_trash', kwargs={
|
|
||||||
'pk': self.test_document.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _request_document_trash_post_view(self):
|
|
||||||
return self.post(
|
|
||||||
viewname='documents:document_trash', kwargs={
|
|
||||||
'pk': self.test_document.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _request_trashed_document_restore_get_view(self):
|
|
||||||
return self.get(
|
|
||||||
viewname='documents:document_restore', kwargs={
|
|
||||||
'pk': self.test_document.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _request_trashed_document_restore_post_view(self):
|
|
||||||
return self.post(
|
|
||||||
viewname='documents:document_restore', kwargs={
|
|
||||||
'pk': self.test_document.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _request_trashed_document_delete_get_view(self):
|
|
||||||
return self.get(
|
|
||||||
viewname='documents:document_delete', kwargs={
|
|
||||||
'pk': self.test_document.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _request_trashed_document_delete_post_view(self):
|
|
||||||
return self.post(
|
|
||||||
viewname='documents:document_delete', kwargs={
|
|
||||||
'pk': self.test_document.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _request_trashed_document_list_view(self):
|
|
||||||
return self.get(viewname='documents:document_list_deleted')
|
|
||||||
|
|
||||||
|
|
||||||
class TrashedDocumentTestCase(
|
|
||||||
TrashedDocumentTestMixin, GenericDocumentViewTestCase
|
|
||||||
):
|
):
|
||||||
def test_document_trash_get_view_no_permissions(self):
|
def test_document_trash_get_view_no_permissions(self):
|
||||||
document_count = Document.objects.count()
|
document_count = Document.objects.count()
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class DocumentTrashView(MultipleObjectConfirmActionView):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def object_action(self, form, instance):
|
def object_action(self, form, instance):
|
||||||
instance.delete()
|
instance.delete(_user=self.request.user)
|
||||||
|
|
||||||
|
|
||||||
class EmptyTrashCanView(ConfirmView):
|
class EmptyTrashCanView(ConfirmView):
|
||||||
|
|||||||
Reference in New Issue
Block a user