diff --git a/mayan/apps/appearance/templates/appearance/home.html b/mayan/apps/appearance/templates/appearance/home.html
index fe62f73bac..dee907106d 100644
--- a/mayan/apps/appearance/templates/appearance/home.html
+++ b/mayan/apps/appearance/templates/appearance/home.html
@@ -40,7 +40,7 @@
{% get_menu_links 'front page menu' as resolved_links %}
{% with 'navigation/large_button_link.html' as link_template %}
- {% with 'col-xs-12 col-sm-6 col-md-4 col-lg-4' as div_class %}
+ {% with 'col-xs-12 col-sm-6 col-md-4 col-lg-3' as div_class %}
{% for object_navigation_links in resolved_links %}
{% include 'navigation/generic_navigation.html' %}
{% endfor %}
diff --git a/mayan/apps/documents/apps.py b/mayan/apps/documents/apps.py
index 43648058ce..082cdb5e20 100644
--- a/mayan/apps/documents/apps.py
+++ b/mayan/apps/documents/apps.py
@@ -31,26 +31,27 @@ from .links import (
link_document_delete, link_document_document_type_edit,
link_document_events_view, link_document_multiple_document_type_edit,
link_document_download, link_document_edit, link_document_list,
- link_document_list_recent, link_document_multiple_delete,
- link_document_multiple_clear_transformations,
- link_document_multiple_download, link_document_multiple_update_page_count,
+ link_document_list_deleted, link_document_list_recent,
+ link_document_multiple_delete, link_document_multiple_clear_transformations,
+ link_document_multiple_download, link_document_multiple_restore,
+ link_document_multiple_update_page_count,
link_document_page_navigation_first, link_document_page_navigation_last,
- link_document_page_navigation_next,
- link_document_page_navigation_previous, link_document_page_return,
- link_document_page_rotate_left, link_document_page_rotate_right,
- link_document_page_view, link_document_page_view_reset,
- link_document_page_zoom_in, link_document_page_zoom_out,
- link_document_pages, link_document_preview, link_document_print,
- link_document_properties, link_document_type_create,
- link_document_type_delete, link_document_type_edit,
- link_document_type_filename_create, link_document_type_filename_delete,
- link_document_type_filename_edit, link_document_type_filename_list,
- link_document_type_list, link_document_type_setup,
- link_document_update_page_count, link_document_version_download,
- link_document_version_list, link_document_version_revert
+ link_document_page_navigation_next, link_document_page_navigation_previous,
+ link_document_page_return, link_document_page_rotate_left,
+ link_document_page_rotate_right, link_document_page_view,
+ link_document_page_view_reset, link_document_page_zoom_in,
+ link_document_page_zoom_out, link_document_pages, link_document_preview,
+ link_document_print, link_document_properties, link_document_restore,
+ link_document_type_create, link_document_type_delete,
+ link_document_type_edit, link_document_type_filename_create,
+ link_document_type_filename_delete, link_document_type_filename_edit,
+ link_document_type_filename_list, link_document_type_list,
+ link_document_type_setup, link_document_update_page_count,
+ link_document_version_download, link_document_version_list,
+ link_document_version_revert
)
from .models import (
- Document, DocumentPage, DocumentType, DocumentTypeFilename,
+ DeletedDocument, Document, DocumentPage, DocumentType, DocumentTypeFilename,
DocumentVersion
)
from .permissions import (
@@ -90,7 +91,7 @@ class DocumentsApp(MayanAppConfig):
)
)
- menu_front_page.bind_links(links=[link_document_list_recent, link_document_list])
+ menu_front_page.bind_links(links=[link_document_list_recent, link_document_list, link_document_list_deleted])
menu_setup.bind_links(links=[link_document_type_setup])
menu_tools.bind_links(links=[link_clear_image_cache])
@@ -102,6 +103,7 @@ class DocumentsApp(MayanAppConfig):
# Document object links
menu_object.bind_links(links=[link_document_edit, link_document_document_type_edit, link_document_print, link_document_delete, link_document_download, link_document_clear_transformations, link_document_update_page_count], sources=[Document])
+ menu_object.bind_links(links=[link_document_restore], sources=[DeletedDocument])
# Document facet links
menu_facet.bind_links(links=[link_acl_list], sources=[Document])
@@ -113,6 +115,7 @@ class DocumentsApp(MayanAppConfig):
# Document actions
menu_object.bind_links(links=[link_document_version_revert, link_document_version_download], sources=[DocumentVersion])
menu_multi_item.bind_links(links=[link_document_multiple_clear_transformations, link_document_multiple_delete, link_document_multiple_download, link_document_multiple_update_page_count, link_document_multiple_document_type_edit], sources=[Document])
+ menu_multi_item.bind_links(links=[link_document_multiple_restore], sources=[DeletedDocument])
# Document pages
menu_facet.bind_links(links=[link_document_page_rotate_left, link_document_page_rotate_right, link_document_page_zoom_in, link_document_page_zoom_out, link_document_page_view_reset], sources=['documents:document_page_view'])
diff --git a/mayan/apps/documents/links.py b/mayan/apps/documents/links.py
index c4fdc3ca0a..6756512e03 100644
--- a/mayan/apps/documents/links.py
+++ b/mayan/apps/documents/links.py
@@ -9,10 +9,10 @@ from navigation import Link
from .permissions import (
permission_document_delete, permission_document_download,
permission_document_properties_edit, permission_document_print,
- permission_document_tools, permission_document_version_revert,
- permission_document_view, permission_document_type_create,
- permission_document_type_delete, permission_document_type_edit,
- permission_document_type_view
+ permission_document_restore, permission_document_tools,
+ permission_document_version_revert, permission_document_view,
+ permission_document_type_create, permission_document_type_delete,
+ permission_document_type_edit, permission_document_type_view
)
from .settings import setting_zoom_max_level, setting_zoom_min_level
@@ -52,17 +52,20 @@ link_document_document_type_edit = Link(permissions=[permission_document_propert
link_document_download = Link(permissions=[permission_document_download], text=_('Download'), view='documents:document_download', args='object.id')
link_document_print = Link(permissions=[permission_document_print], text=_('Print'), view='documents:document_print', args='object.id')
link_document_update_page_count = Link(permissions=[permission_document_tools], text=_('Reset page count'), view='documents:document_update_page_count', args='object.pk')
-
-# Views
-link_document_list = Link(icon='fa fa-file', text=_('All documents'), view='documents:document_list')
-link_document_list_recent = Link(icon='fa fa-clock-o', text=_('Recent documents'), view='documents:document_list_recent')
+link_document_restore = Link(permissions=[permission_document_restore], text=_('Restore'), view='documents:document_restore', args='object.pk')
link_document_multiple_clear_transformations = Link(permissions=[permission_transformation_delete], text=_('Clear transformations'), view='documents:document_multiple_clear_transformations')
link_document_multiple_delete = Link(permissions=[permission_document_delete], tags='dangerous', text=_('Delete'), view='documents:document_multiple_delete')
link_document_multiple_document_type_edit = Link(permissions=[permission_document_properties_edit], text=_('Change type'), view='documents:document_multiple_document_type_edit')
link_document_multiple_download = Link(permissions=[permission_document_download], text=_('Download'), view='documents:document_multiple_download')
link_document_multiple_update_page_count = Link(permissions=[permission_document_tools], text=_('Reset page count'), view='documents:document_multiple_update_page_count')
+link_document_multiple_restore = Link(permissions=[permission_document_restore], text=_('Restore'), view='documents:document_multiple_restore')
link_document_version_download = Link(args='object.pk', permissions=[permission_document_download], text=_('Download'), view='documents:document_version_download')
+# Views
+link_document_list = Link(icon='fa fa-file', text=_('All documents'), view='documents:document_list')
+link_document_list_recent = Link(icon='fa fa-clock-o', text=_('Recent documents'), view='documents:document_list_recent')
+link_document_list_deleted = Link(icon='fa fa-trash', text=_('Deleted documents'), view='documents:document_list_deleted')
+
# Tools
link_clear_image_cache = Link(
icon='fa fa-file-image-o',
@@ -97,5 +100,3 @@ link_document_type_filename_edit = Link(permissions=[permission_document_type_ed
link_document_type_filename_list = Link(permissions=[permission_document_type_view], text=_('Filenames'), view='documents:document_type_filename_list', args='resolved_object.id')
link_document_type_list = Link(permissions=[permission_document_type_view], text=_('Document types'), view='documents:document_type_list')
link_document_type_setup = Link(icon='fa fa-file', permissions=[permission_document_type_view], text=_('Document types'), view='documents:document_type_list')
-
-link_tools = Link
diff --git a/mayan/apps/documents/managers.py b/mayan/apps/documents/managers.py
index 5bdb31e896..f8a9861978 100644
--- a/mayan/apps/documents/managers.py
+++ b/mayan/apps/documents/managers.py
@@ -37,6 +37,9 @@ class DocumentTypeManager(models.Manager):
class DocumentManager(models.Manager):
+ def get_queryset(self):
+ return TrashCanQuerySet(self.model, using=self._db).filter(in_trash=False)
+
def invalidate_cache(self):
for document in self.model.objects.all():
document.invalidate_cache()
@@ -73,3 +76,20 @@ class DocumentManager(models.Manager):
version = document.new_version(file_object=file_object, user=user)
document.set_document_type(document_type, force=True)
return version
+
+
+class TrashCanManager(models.Manager):
+ def get_queryset(self):
+ return super(TrashCanManager, self).get_queryset().filter(in_trash=True)
+
+
+class TrashCanQuerySet(models.QuerySet):
+ def delete(self, to_trash=True):
+ for instance in self:
+ instance.delete(to_trash=to_trash)
+
+ #if to_trash:
+ # for instance in self:
+ # instance.delete(to_trash=to_trash)
+ #else:
+ # super(TrashCanQuerySet, self).delete()
diff --git a/mayan/apps/documents/migrations/0009_document_in_trash.py b/mayan/apps/documents/migrations/0009_document_in_trash.py
new file mode 100644
index 0000000000..281c0d165d
--- /dev/null
+++ b/mayan/apps/documents/migrations/0009_document_in_trash.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('documents', '0008_auto_20150624_0520'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='document',
+ name='in_trash',
+ field=models.BooleanField(default=False, verbose_name='In trash?', editable=False),
+ preserve_default=True,
+ ),
+ ]
diff --git a/mayan/apps/documents/models.py b/mayan/apps/documents/models.py
index 764be073f8..aa55ff0ed0 100644
--- a/mayan/apps/documents/models.py
+++ b/mayan/apps/documents/models.py
@@ -28,7 +28,7 @@ from .events import (
event_document_version_revert
)
from .managers import (
- DocumentManager, DocumentTypeManager, RecentDocumentManager
+ DocumentManager, DocumentTypeManager, RecentDocumentManager, TrashCanManager
)
from .runtime import storage_backend
from .settings import (
@@ -79,6 +79,7 @@ class Document(models.Model):
description = models.TextField(blank=True, null=True, verbose_name=_('Description'))
date_added = models.DateTimeField(verbose_name=_('Added'), auto_now_add=True)
language = models.CharField(choices=setting_language_choices.value, default=setting_language.value, max_length=8, verbose_name=_('Language'))
+ in_trash = models.BooleanField(default=False, editable=False, verbose_name=_('In trash?'))
objects = DocumentManager()
@@ -121,9 +122,18 @@ class Document(models.Model):
RecentDocument.objects.add_document_for_user(user, self)
def delete(self, *args, **kwargs):
- for version in self.versions.all():
- version.delete()
- return super(Document, self).delete(*args, **kwargs)
+ if not self.in_trash and kwargs.get('to_trash', True):
+ self.in_trash = True
+ self.save()
+ else:
+ for version in self.versions.all():
+ version.delete()
+
+ return super(Document, self).delete(*args, **kwargs)
+
+ def restore(self):
+ self.in_trash = False
+ self.save()
@property
def size(self):
@@ -209,6 +219,13 @@ class Document(models.Model):
return self.save_to_file(temporary_path, buffer_size)
+class DeletedDocument(Document):
+ objects = TrashCanManager()
+
+ class Meta:
+ proxy = True
+
+
@python_2_unicode_compatible
class DocumentVersion(models.Model):
"""
diff --git a/mayan/apps/documents/permissions.py b/mayan/apps/documents/permissions.py
index d7df396c57..9597b10597 100644
--- a/mayan/apps/documents/permissions.py
+++ b/mayan/apps/documents/permissions.py
@@ -13,6 +13,7 @@ permission_document_edit = namespace.add_permission(name='document_edit', label=
permission_document_new_version = namespace.add_permission(name='document_new_version', label=_('Create new document versions'))
permission_document_properties_edit = namespace.add_permission(name='document_properties_edit', label=_('Edit document properties'))
permission_document_print = namespace.add_permission(name='document_print', label=_('Can print documents'))
+permission_document_restore = namespace.add_permission(name='document_restore', label=_('Restore deleted document'))
permission_document_tools = namespace.add_permission(name='document_tools', label=_('Execute document modifying tools'))
permission_document_version_revert = namespace.add_permission(name='document_version_revert', label=_('Revert documents to a previous version'))
permission_document_view = namespace.add_permission(name='document_view', label=_('View documents'))
diff --git a/mayan/apps/documents/urls.py b/mayan/apps/documents/urls.py
index e3309aac41..cffb88b2e3 100644
--- a/mayan/apps/documents/urls.py
+++ b/mayan/apps/documents/urls.py
@@ -11,16 +11,20 @@ from .api_views import (
)
from .settings import setting_print_size, setting_display_size
from .views import (
- DocumentListView, DocumentPageListView, RecentDocumentListView
+ DeletedDocumentListView, DocumentListView, DocumentManyRestoreView,
+ DocumentPageListView, DocumentRestoreView, RecentDocumentListView
)
urlpatterns = patterns(
'documents.views',
url(r'^list/$', DocumentListView.as_view(), name='document_list'),
url(r'^list/recent/$', RecentDocumentListView.as_view(), name='document_list_recent'),
+ url(r'^list/deleted/$', DeletedDocumentListView.as_view(), name='document_list_deleted'),
url(r'^(?P
\d+)/preview/$', 'document_preview', name='document_preview'),
url(r'^(?P\d+)/properties/$', 'document_properties', name='document_properties'),
+ url(r'^(?P\d+)/restore/$', DocumentRestoreView.as_view(), name='document_restore'),
+ url(r'^multiple/restore/$', DocumentManyRestoreView.as_view(), name='document_multiple_restore'),
url(r'^(?P\d+)/type/$', 'document_document_type_edit', name='document_document_type_edit'),
url(r'^multiple/type/$', 'document_multiple_document_type_edit', name='document_multiple_document_type_edit'),
url(r'^(?P\d+)/delete/$', 'document_delete', name='document_delete'),
diff --git a/mayan/apps/documents/views.py b/mayan/apps/documents/views.py
index c1efeae8b6..8e659d9082 100644
--- a/mayan/apps/documents/views.py
+++ b/mayan/apps/documents/views.py
@@ -16,8 +16,9 @@ from django.utils.translation import ugettext_lazy as _, ungettext
from acls.models import AccessControlList
from common.compressed_files import CompressedFile
+from common.mixins import MultipleInstanceActionMixin
from common.utils import encapsulate, pretty_size
-from common.views import ParentChildListView, SingleObjectListView
+from common.views import ConfirmView, ParentChildListView, SingleObjectListView
from common.widgets import two_state_template
from converter.literals import (
DEFAULT_PAGE_NUMBER, DEFAULT_ROTATION, DEFAULT_ZOOM_LEVEL
@@ -37,16 +38,16 @@ from .forms import (
)
from .literals import DOCUMENT_IMAGE_TASK_TIMEOUT
from .models import (
- Document, DocumentType, DocumentPage, DocumentTypeFilename,
+ DeletedDocument, Document, DocumentType, DocumentPage, DocumentTypeFilename,
DocumentVersion, RecentDocument
)
from .permissions import (
permission_document_delete, permission_document_download,
permission_document_print, permission_document_properties_edit,
- permission_document_tools, permission_document_type_create,
- permission_document_type_delete, permission_document_type_edit,
- permission_document_type_view, permission_document_version_revert,
- permission_document_view,
+ permission_document_restore, permission_document_tools,
+ permission_document_type_create, permission_document_type_delete,
+ permission_document_type_edit, permission_document_type_view,
+ permission_document_version_revert, permission_document_view,
)
from .settings import (
setting_preview_size, setting_recent_count, setting_rotation_step,
@@ -76,6 +77,16 @@ class DocumentListView(SingleObjectListView):
return super(DocumentListView, self).get_queryset()
+class DeletedDocumentListView(DocumentListView):
+ extra_context = {
+ 'hide_link': True,
+ 'title': _('Deleted documents'),
+ }
+
+ def get_document_queryset(self):
+ return DeletedDocument.objects.all()
+
+
class DocumentPageListView(ParentChildListView):
object_permission = permission_document_view
parent_queryset = Document.objects.all()
@@ -106,6 +117,36 @@ class RecentDocumentListView(DocumentListView):
return RecentDocument.objects.get_for_user(self.request.user)
+class DocumentRestoreView(ConfirmView):
+ extra_context = {
+ 'title': _('Restore the selected document?')
+ }
+
+ def object_action(self, request, instance):
+ try:
+ Permission.check_permissions(request.user, [permission_document_restore])
+ except PermissionDenied:
+ AccessControlList.objects.check_access(permission_document_restore, request.user, instance)
+
+ instance.restore()
+ messages.success(request, _('Document: %(document)s restored.') % {
+ 'document': instance}
+ )
+
+ def post(self, request, *args, **kwargs):
+ document = get_object_or_404(DeletedDocument, pk=self.kwargs['pk'])
+ self.object_action(request=request, instance=document)
+
+ return HttpResponseRedirect(self.get_success_url())
+
+
+class DocumentManyRestoreView(MultipleInstanceActionMixin, DocumentRestoreView):
+ extra_context = {
+ 'title': _('Restore the selected documents?')
+ }
+ model = DeletedDocument
+
+
def document_properties(request, document_id):
document = get_object_or_404(Document, pk=document_id)