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)