diff --git a/mayan/apps/sources/apps.py b/mayan/apps/sources/apps.py index ccfcc630d5..16510adc24 100644 --- a/mayan/apps/sources/apps.py +++ b/mayan/apps/sources/apps.py @@ -22,13 +22,13 @@ from .handlers import ( handler_create_default_document_source, handler_initialize_periodic_tasks ) from .links import ( - link_document_create_multiple, link_setup_source_check_now, - link_setup_source_create_imap_email, link_setup_source_create_pop3_email, - link_setup_source_create_sane_scanner, - link_setup_source_create_staging_folder, - link_setup_source_create_watch_folder, link_setup_source_create_webform, - link_setup_source_delete, link_setup_source_edit, link_setup_source_logs, - link_setup_sources, link_staging_file_delete, link_upload_version + link_document_create_multiple, link_source_check_now, + link_source_create_imap_email, link_source_create_pop3_email, + link_source_create_sane_scanner, + link_source_create_staging_folder, + link_source_create_watch_folder, link_source_create_webform, + link_source_delete, link_source_edit, link_source_logs, + link_source_list, link_staging_file_delete, link_upload_version ) from .queues import * # NOQA from .widgets import StagingFileThumbnailWidget @@ -45,24 +45,24 @@ class SourcesApp(MayanAppConfig): def ready(self): super(SourcesApp, self).ready() - POP3Email = self.get_model('POP3Email') - IMAPEmail = self.get_model('IMAPEmail') - Source = self.get_model('Source') - SourceLog = self.get_model('SourceLog') - SaneScanner = self.get_model('SaneScanner') - StagingFolderSource = self.get_model('StagingFolderSource') - WatchFolderSource = self.get_model('WatchFolderSource') - WebFormSource = self.get_model('WebFormSource') + POP3Email = self.get_model(model_name='POP3Email') + IMAPEmail = self.get_model(model_name='IMAPEmail') + Source = self.get_model(model_name='Source') + SourceLog = self.get_model(model_name='SourceLog') + SaneScanner = self.get_model(model_name='SaneScanner') + StagingFolderSource = self.get_model(model_name='StagingFolderSource') + WatchFolderSource = self.get_model(model_name='WatchFolderSource') + WebFormSource = self.get_model(model_name='WebFormSource') MissingItem( - label=_('Create a document source'), + condition=lambda: not Source.objects.exists(), description=_( 'Document sources are the way in which new documents are ' 'feed to Mayan EDMS, create at least a web form source to ' 'be able to upload documents from a browser.' ), - condition=lambda: not Source.objects.exists(), - view='sources:setup_source_list' + label=_('Create a document source'), + view='sources:source_list' ) SourceColumn( @@ -83,11 +83,11 @@ class SourcesApp(MayanAppConfig): ) html_widget = StagingFileThumbnailWidget() SourceColumn( - source=StagingFile, - label=_('Thumbnail'), func=lambda context: html_widget.render( instance=context['object'], - ) + ), + label=_('Thumbnail'), + source=StagingFile ) SourceColumn( @@ -134,7 +134,7 @@ class SourcesApp(MayanAppConfig): menu_list_facet.bind_links( links=( - link_setup_source_logs, link_transformation_list, + link_source_logs, link_transformation_list, ), sources=( POP3Email, IMAPEmail, SaneScanner, StagingFolderSource, WatchFolderSource, WebFormSource @@ -143,7 +143,7 @@ class SourcesApp(MayanAppConfig): menu_object.bind_links( links=( - link_setup_source_edit, link_setup_source_delete, + link_source_edit, link_source_delete, ), sources=( POP3Email, IMAPEmail, SaneScanner, StagingFolderSource, WatchFolderSource, WebFormSource @@ -153,24 +153,24 @@ class SourcesApp(MayanAppConfig): links=(link_staging_file_delete,), sources=(StagingFile,) ) menu_object.bind_links( - links=(link_setup_source_check_now,), + links=(link_source_check_now,), sources=(IMAPEmail, POP3Email, WatchFolderSource,) ) menu_secondary.bind_links( links=( - link_setup_sources, link_setup_source_create_webform, - link_setup_source_create_sane_scanner, - link_setup_source_create_staging_folder, - link_setup_source_create_pop3_email, - link_setup_source_create_imap_email, - link_setup_source_create_watch_folder + link_source_list, link_source_create_webform, + link_source_create_sane_scanner, + link_source_create_staging_folder, + link_source_create_pop3_email, + link_source_create_imap_email, + link_source_create_watch_folder ), sources=( POP3Email, IMAPEmail, StagingFolderSource, WatchFolderSource, - WebFormSource, 'sources:setup_source_list', - 'sources:setup_source_create' + WebFormSource, 'sources:source_list', + 'sources:source_create' ) ) - menu_setup.bind_links(links=(link_setup_sources,)) + menu_setup.bind_links(links=(link_source_list,)) menu_sidebar.bind_links( links=(link_upload_version,), sources=( diff --git a/mayan/apps/sources/icons.py b/mayan/apps/sources/icons.py index 087e65272f..0c2d77ee3f 100644 --- a/mayan/apps/sources/icons.py +++ b/mayan/apps/sources/icons.py @@ -6,8 +6,8 @@ icon_document_create_multiple = Icon( driver_name='fontawesome', symbol='upload' ) icon_log = Icon(driver_name='fontawesome', symbol='exclamation-triangle') -icon_setup_sources = Icon(driver_name='fontawesome', symbol='upload') icon_source_create = Icon(driver_name='fontawesome', symbol='plus') +icon_source_list = Icon(driver_name='fontawesome', symbol='upload') icon_staging_folder = Icon(driver_name='fontawesome', symbol='folder') icon_upload_view_link = Icon(driver_name='fontawesome', symbol='upload') icon_wizard_submit = Icon(driver_name='fontawesome', symbol='arrow-right') diff --git a/mayan/apps/sources/links.py b/mayan/apps/sources/links.py index 91170d22b4..66b71d2ac8 100644 --- a/mayan/apps/sources/links.py +++ b/mayan/apps/sources/links.py @@ -9,7 +9,7 @@ from mayan.apps.documents.permissions import ( from mayan.apps.navigation import Link from .icons import ( - icon_document_create_multiple, icon_log, icon_setup_sources, + icon_document_create_multiple, icon_log, icon_source_list, icon_source_create ) from .literals import ( @@ -18,8 +18,8 @@ from .literals import ( SOURCE_CHOICE_WEB_FORM ) from .permissions import ( - permission_sources_setup_create, permission_sources_setup_delete, - permission_sources_setup_edit, permission_sources_setup_view + permission_sources_create, permission_sources_delete, + permission_sources_edit, permission_sources_view ) @@ -31,13 +31,10 @@ def condition_check_document_creation_acls(context): app_label='documents', model_name='DocumentType' ) - queryset = AccessControlList.objects.filter_by_access( - permission=permission_document_create, user=context['user'], - queryset=DocumentType.objects.all() - ) - - if queryset: - return True + return AccessControlList.objects.restrict_queryset( + permission=permission_document_create, + queryset=DocumentType.objects.all(), user=context['user'] + ).exists() def document_new_version_not_blocked(context): @@ -53,72 +50,77 @@ link_document_create_multiple = Link( icon_class=icon_document_create_multiple, text=_('New document'), view='sources:document_create_multiple' ) -link_setup_sources = Link( - icon_class=icon_setup_sources, - permissions=(permission_sources_setup_view,), text=_('Sources'), - view='sources:setup_source_list' +link_source_check_now = Link( + kwargs={'source_id': 'resolved_object.pk'}, + permissions=(permission_sources_edit,), text=_('Check now'), + view='sources:source_check' ) -link_setup_source_create_imap_email = Link( - args='"%s"' % SOURCE_CHOICE_EMAIL_IMAP, icon_class=icon_source_create, - permissions=(permission_sources_setup_create,), - text=_('Add new IMAP email'), view='sources:setup_source_create', +link_source_create_imap_email = Link( + icon_class=icon_source_create, + kwargs={'source_type': '"%s"' % SOURCE_CHOICE_EMAIL_IMAP}, + permissions=(permission_sources_create,), + text=_('Add new IMAP email'), view='sources:source_create' ) -link_setup_source_create_pop3_email = Link( - args='"%s"' % SOURCE_CHOICE_EMAIL_POP3, icon_class=icon_source_create, - permissions=(permission_sources_setup_create,), - text=_('Add new POP3 email'), view='sources:setup_source_create', +link_source_create_pop3_email = Link( + icon_class=icon_source_create, + kwargs={'source_type': '"%s"' % SOURCE_CHOICE_EMAIL_POP3}, + permissions=(permission_sources_create,), + text=_('Add new POP3 email'), view='sources:source_create' ) -link_setup_source_create_staging_folder = Link( - args='"%s"' % SOURCE_CHOICE_STAGING, icon_class=icon_source_create, - permissions=(permission_sources_setup_create,), - text=_('Add new staging folder'), view='sources:setup_source_create', +link_source_create_staging_folder = Link( + icon_class=icon_source_create, + kwargs={'source_type': '"%s"' % SOURCE_CHOICE_STAGING}, + permissions=(permission_sources_create,), + text=_('Add new staging folder'), view='sources:source_create' ) -link_setup_source_create_watch_folder = Link( - args='"%s"' % SOURCE_CHOICE_WATCH, icon_class=icon_source_create, - permissions=(permission_sources_setup_create,), - text=_('Add new watch folder'), view='sources:setup_source_create', +link_source_create_watch_folder = Link( + icon_class=icon_source_create, + kwargs={'source_type': '"%s"' % SOURCE_CHOICE_WATCH}, + permissions=(permission_sources_create,), + text=_('Add new watch folder'), view='sources:source_create' ) -link_setup_source_create_webform = Link( - args='"%s"' % SOURCE_CHOICE_WEB_FORM, icon_class=icon_source_create, - permissions=(permission_sources_setup_create,), - text=_('Add new webform source'), view='sources:setup_source_create', +link_source_create_webform = Link( + icon_class=icon_source_create, + kwargs={'source_type': '"%s"' % SOURCE_CHOICE_WEB_FORM}, + permissions=(permission_sources_create,), + text=_('Add new webform source'), view='sources:source_create' ) -link_setup_source_create_sane_scanner = Link( - args='"%s"' % SOURCE_CHOICE_SANE_SCANNER, icon_class=icon_source_create, - permissions=(permission_sources_setup_create,), - text=_('Add new SANE scanner'), view='sources:setup_source_create', +link_source_create_sane_scanner = Link( + icon_class=icon_source_create, + kwargs={'source_type': '"%s"' % SOURCE_CHOICE_SANE_SCANNER}, + permissions=(permission_sources_create,), + text=_('Add new SANE scanner'), view='sources:source_create' ) -link_setup_source_delete = Link( - args=('resolved_object.pk',), - permissions=(permission_sources_setup_delete,), tags='dangerous', - text=_('Delete'), view='sources:setup_source_delete', +link_source_delete = Link( + kwargs={'source_id': 'resolved_object.pk'}, + permissions=(permission_sources_delete,), tags='dangerous', + text=_('Delete'), view='sources:source_delete' ) -link_setup_source_edit = Link( - args=('resolved_object.pk',), - permissions=(permission_sources_setup_edit,), text=_('Edit'), - view='sources:setup_source_edit', +link_source_edit = Link( + kwargs={'source_id': 'resolved_object.pk'}, + permissions=(permission_sources_edit,), text=_('Edit'), + view='sources:source_edit' ) link_source_list = Link( - permissions=(permission_sources_setup_view,), text=_('Document sources'), - view='sources:setup_web_form_list' + icon_class=icon_source_list, + permissions=(permission_sources_view,), text=_('Sources'), + view='sources:source_list' +) +link_source_logs = Link( + icon_class=icon_log, kwargs={'source_id': 'resolved_object.pk'}, + permissions=(permission_sources_view,), text=_('Logs'), + view='sources:source_logs' ) link_staging_file_delete = Link( - args=('source.pk', 'object.encoded_filename',), keep_query=True, - permissions=(permission_document_new_version, permission_document_create), - tags='dangerous', text=_('Delete'), view='sources:staging_file_delete', + keep_query=True, kwargs={ + 'staging_file_pk': 'source.pk', + 'encoded_filename': 'object.encoded_filename' + }, permissions=(permission_document_new_version, permission_document_create), + tags='dangerous', text=_('Delete'), view='sources:staging_file_delete' ) link_upload_version = Link( - args='resolved_object.pk', condition=document_new_version_not_blocked, + condition=document_new_version_not_blocked, + kwargs={'document_pk': 'resolved_object.pk'}, permissions=(permission_document_new_version,), - text=_('Upload new version'), view='sources:upload_version', -) -link_setup_source_logs = Link( - args=('resolved_object.pk',), icon_class=icon_log, - permissions=(permission_sources_setup_view,), text=_('Logs'), - view='sources:setup_source_logs', -) -link_setup_source_check_now = Link( - args=('resolved_object.pk',), - permissions=(permission_sources_setup_view,), text=_('Check now'), - view='sources:setup_source_check', + text=_('Upload new version'), view='sources:upload_version' ) diff --git a/mayan/apps/sources/literals.py b/mayan/apps/sources/literals.py index 40097ba537..039bb4c452 100644 --- a/mayan/apps/sources/literals.py +++ b/mayan/apps/sources/literals.py @@ -1,7 +1,9 @@ from __future__ import unicode_literals +import os import platform +from django.conf import settings from django.utils.translation import ugettext_lazy as _ if platform.system() == 'OpenBSD': @@ -16,6 +18,11 @@ DEFAULT_POP3_TIMEOUT = 60 DEFAULT_SOURCE_LOCK_EXPIRE = 600 DEFAULT_SOURCE_TASK_RETRY_DELAY = 10 +DEFAULT_STAGING_FILE_CACHE_STORAGE_BACKEND = 'SOURCES_STAGING_FILE_CACHE_STORAGE_BACKEND' +DEFAULT_STAGING_FILE_CACHE_STORAGE_BACKEND_ARGUMENTS = { + 'location': os.path.join(settings.MEDIA_ROOT, 'staging_file_cache') +} + SCANNER_SOURCE_FLATBED = 'flatbed' SCANNER_SOURCE_ADF = 'Automatic Document Feeder' diff --git a/mayan/apps/sources/permissions.py b/mayan/apps/sources/permissions.py index 94bb90e45b..1f85fba42f 100644 --- a/mayan/apps/sources/permissions.py +++ b/mayan/apps/sources/permissions.py @@ -6,18 +6,18 @@ from mayan.apps.permissions import PermissionNamespace namespace = PermissionNamespace(label=_('Sources setup'), name='source_setup') -permission_sources_setup_create = namespace.add_permission( - name='sources_setup_create', label=_('Create new document sources') +permission_sources_create = namespace.add_permission( + label=_('Create new document sources'), name='sources_setup_create' ) -permission_sources_setup_delete = namespace.add_permission( - name='sources_setup_delete', label=_('Delete document sources') +permission_sources_delete = namespace.add_permission( + label=_('Delete document sources'), name='sources_setup_delete' ) -permission_sources_setup_edit = namespace.add_permission( - name='sources_setup_edit', label=_('Edit document sources') +permission_sources_edit = namespace.add_permission( + label=_('Edit document sources'), name='sources_setup_edit' ) -permission_sources_setup_view = namespace.add_permission( - name='sources_setup_view', label=_('View existing document sources') +permission_sources_view = namespace.add_permission( + label=_('View existing document sources'), name='sources_setup_view' ) permission_staging_file_delete = namespace.add_permission( - name='sources_staging_file_delete', label=_('Delete staging files') + label=_('Delete staging files'), name='sources_staging_file_delete' ) diff --git a/mayan/apps/sources/queues.py b/mayan/apps/sources/queues.py index eebc52c0e4..8793292a97 100644 --- a/mayan/apps/sources/queues.py +++ b/mayan/apps/sources/queues.py @@ -5,28 +5,28 @@ from django.utils.translation import ugettext_lazy as _ from mayan.apps.task_manager.classes import CeleryQueue queue_sources = CeleryQueue( - name='sources', label=_('Sources') + label=_('Sources'), name='sources' ) queue_sources_periodic = CeleryQueue( - name='sources_periodic', label=_('Sources periodic'), transient=True + label=_('Sources periodic'), name='sources_periodic', transient=True ) queue_sources_fast = CeleryQueue( - name='sources_fast', label=_('Sources fast'), transient=True + label=_('Sources fast'), name='sources_fast', transient=True ) queue_sources_fast.add_task_type( - name='mayan.apps.sources.tasks.task_generate_staging_file_image', - label=_('Generate staging file image') + label=_('Generate staging file image'), + name='mayan.apps.sources.tasks.task_generate_staging_file_image' ) queue_sources_periodic.add_task_type( - name='mayan.apps.sources.tasks.task_check_interval_source', - label=_('Check interval source') + label=_('Check interval source'), + name='mayan.apps.sources.tasks.task_check_interval_source' ) queue_sources.add_task_type( - name='mayan.apps.sources.tasks.task_source_handle_upload', - label=_('Handle upload') + label=_('Handle upload'), + name='mayan.apps.sources.tasks.task_source_handle_upload' ) queue_sources.add_task_type( - name='mayan.apps.sources.tasks.task_upload_document', - label=_('Upload document') + label=_('Upload document'), + name='mayan.apps.sources.tasks.task_upload_document' ) diff --git a/mayan/apps/sources/settings.py b/mayan/apps/sources/settings.py index 4bf8778ac7..ae124f4959 100644 --- a/mayan/apps/sources/settings.py +++ b/mayan/apps/sources/settings.py @@ -1,15 +1,15 @@ from __future__ import unicode_literals -import os - -from django.conf import settings from django.utils.translation import ugettext_lazy as _ from mayan.apps.smart_settings import Namespace -from .literals import DEFAULT_SCANIMAGE_PATH +from .literals import ( + DEFAULT_SCANIMAGE_PATH, DEFAULT_STAGING_FILE_CACHE_STORAGE_BACKEND, + DEFAULT_STAGING_FILE_CACHE_STORAGE_BACKEND_ARGUMENTS +) -namespace = Namespace(name='sources', label=_('Sources')) +namespace = Namespace(label=_('Sources'), name='sources') setting_scanimage_path = namespace.add_setting( global_name='SOURCES_SCANIMAGE_PATH', default=DEFAULT_SCANIMAGE_PATH, @@ -19,7 +19,7 @@ setting_scanimage_path = namespace.add_setting( is_path=True ) setting_staging_file_image_cache_storage = namespace.add_setting( - global_name='SOURCES_STAGING_FILE_CACHE_STORAGE_BACKEND', + global_name=DEFAULT_STAGING_FILE_CACHE_STORAGE_BACKEND, default='django.core.files.storage.FileSystemStorage', help_text=_( 'Path to the Storage subclass to use when storing the cached ' 'staging_file image files.' @@ -27,9 +27,7 @@ setting_staging_file_image_cache_storage = namespace.add_setting( ) setting_staging_file_image_cache_storage_arguments = namespace.add_setting( global_name='SOURCES_STAGING_FILE_CACHE_STORAGE_BACKEND_ARGUMENTS', - default={ - 'location': os.path.join(settings.MEDIA_ROOT, 'staging_file_cache') - }, help_text=_( + default=DEFAULT_STAGING_FILE_CACHE_STORAGE_BACKEND_ARGUMENTS, help_text=_( 'Arguments to pass to the SOURCES_STAGING_FILE_CACHE_STORAGE_BACKEND.' ) ) diff --git a/mayan/apps/sources/tests/test_views.py b/mayan/apps/sources/tests/test_views.py index 45989d89da..fbaa09fc21 100644 --- a/mayan/apps/sources/tests/test_views.py +++ b/mayan/apps/sources/tests/test_views.py @@ -18,8 +18,8 @@ from ..links import link_upload_version from ..literals import SOURCE_CHOICE_WEB_FORM from ..models import StagingFolderSource, WebFormSource from ..permissions import ( - permission_sources_setup_create, permission_sources_setup_delete, - permission_sources_setup_view, permission_staging_file_delete + permission_sources_create, permission_sources_delete, + permission_sources_view, permission_staging_file_delete ) from .literals import ( @@ -33,7 +33,6 @@ class DocumentUploadTestCase(GenericDocumentViewTestCase): def setUp(self): super(DocumentUploadTestCase, self).setUp() self._create_source() - self.login_user() def _create_source(self): self.source = WebFormSource.objects.create( @@ -55,7 +54,7 @@ class DocumentUploadTestCase(GenericDocumentViewTestCase): def test_upload_wizard_without_permission(self): response = self._request_upload_wizard_view() - self.assertEqual(response.status_code, 403) + self.assertEqual(response.status_code, 404) self.assertEqual(Document.objects.count(), 0) def test_upload_wizard_with_permission(self): @@ -112,12 +111,12 @@ class DocumentUploadIssueTestCase(GenericDocumentViewTestCase): auto_upload_document = False def test_issue_25(self): - self.login_admin_user() + self.login_superuser() # Create new webform source self.post( - viewname='sources:setup_source_create', - args=(SOURCE_CHOICE_WEB_FORM,), + viewname='sources:source_create', + kwargs={'source_type': SOURCE_CHOICE_WEB_FORM}, data={'label': 'test', 'uncompress': 'n', 'enabled': True} ) self.assertEqual(WebFormSource.objects.count(), 1) @@ -145,7 +144,8 @@ class DocumentUploadIssueTestCase(GenericDocumentViewTestCase): # Test for issue 25 during editing self.post( - viewname='documents:document_edit', args=(document.pk,), data={ + viewname='documents:document_edit', + kargs={'document_id': document.pk}, data={ 'description': TEST_DOCUMENT_DESCRIPTION, 'language': document.language, 'label': document.label } @@ -171,7 +171,8 @@ class NewDocumentVersionViewTestCase(GenericDocumentViewTestCase): NewVersionBlock.objects.block(self.document) response = self.post( - viewname='sources:upload_version', args=(self.document.pk,), + viewname='sources:upload_version', + kwargs={'document_id': self.document.pk}, follow=True ) @@ -181,7 +182,8 @@ class NewDocumentVersionViewTestCase(GenericDocumentViewTestCase): ) response = self.get( - 'documents:document_version_list', args=(self.document.pk,), + viewname='documents:document_version_list', + kwargs={'document_id': self.document.pk}, follow=True ) @@ -204,13 +206,6 @@ class StagingFolderViewTestCase(GenericViewTestCase): fs_cleanup(self.temporary_directory) super(StagingFolderViewTestCase, self).tearDown() - def _request_staging_file_delete_view(self, staging_file): - return self.post( - viewname='sources:staging_file_delete', args=( - self.staging_folder.pk, staging_file.encoded_filename - ) - ) - def _create_staging_folder(self): self.staging_folder = StagingFolderSource.objects.create( label=TEST_SOURCE_LABEL, @@ -219,6 +214,15 @@ class StagingFolderViewTestCase(GenericViewTestCase): uncompress=TEST_SOURCE_UNCOMPRESS_N, ) + def _request_staging_file_delete_view(self, staging_file): + return self.post( + viewname='sources:staging_file_delete', + kwargs={ + 'staging_folder_id': self.staging_folder.pk, + 'encoded_filename': staging_file.encoded_filename + }, + ) + def test_staging_folder_delete_no_permission(self): self._create_staging_folder() @@ -259,7 +263,7 @@ class SourcesViewsTestCase(GenericViewTestCase): ) def _request_setup_source_list_view(self): - return self.get(viewname='sources:setup_source_list') + return self.get(viewname='sources:source_list') def test_source_list_view_no_permission(self): self._create_web_source() @@ -270,7 +274,7 @@ class SourcesViewsTestCase(GenericViewTestCase): def test_source_list_view_with_permission(self): self._create_web_source() - self.grant_permission(permission=permission_sources_setup_view) + self.grant_permission(permission=permission_sources_view) response = self._request_setup_source_list_view() self.assertContains( @@ -280,14 +284,14 @@ class SourcesViewsTestCase(GenericViewTestCase): def _request_setup_source_create_view(self): return self.post( args=(SOURCE_CHOICE_WEB_FORM,), - viewname='sources:setup_source_create', data={ + viewname='sources:source_create', data={ 'enabled': True, 'label': TEST_SOURCE_LABEL, 'uncompress': TEST_SOURCE_UNCOMPRESS_N } ) def test_source_create_view_no_permission(self): - self.grant_permission(permission=permission_sources_setup_view) + self.grant_permission(permission=permission_sources_view) response = self._request_setup_source_create_view() self.assertEqual(response.status_code, 403) @@ -295,8 +299,8 @@ class SourcesViewsTestCase(GenericViewTestCase): self.assertEqual(WebFormSource.objects.count(), 0) def test_source_create_view_with_permission(self): - self.grant_permission(permission=permission_sources_setup_create) - self.grant_permission(permission=permission_sources_setup_view) + self.grant_permission(permission=permission_sources_create) + self.grant_permission(permission=permission_sources_view) response = self._request_setup_source_create_view() self.assertEquals(response.status_code, 302) @@ -307,15 +311,15 @@ class SourcesViewsTestCase(GenericViewTestCase): def _request_setup_source_delete_view(self): return self.post( - args=(self.source.pk,), - viewname='sources:setup_source_delete' + viewname='sources:source_delete', + kwargs={'source_id': self.source.pk} ) def test_source_delete_view_with_permission(self): self._create_web_source() - self.grant_permission(permission=permission_sources_setup_delete) - self.grant_permission(permission=permission_sources_setup_view) + self.grant_permission(permission=permission_sources_delete) + self.grant_permission(permission=permission_sources_view) response = self._request_setup_source_delete_view() self.assertEqual(response.status_code, 302) @@ -324,7 +328,7 @@ class SourcesViewsTestCase(GenericViewTestCase): def test_source_delete_view_no_permission(self): self._create_web_source() - self.grant_permission(permission=permission_sources_setup_view) + self.grant_permission(permission=permission_sources_view) response = self._request_setup_source_delete_view() self.assertEqual(response.status_code, 403) diff --git a/mayan/apps/sources/urls.py b/mayan/apps/sources/urls.py index a34d9d77eb..da61d77be4 100644 --- a/mayan/apps/sources/urls.py +++ b/mayan/apps/sources/urls.py @@ -7,87 +7,81 @@ from .api_views import ( APIStagingSourceListView, APIStagingSourceView ) from .views import ( - SetupSourceCheckView, SetupSourceCreateView, SetupSourceDeleteView, - SetupSourceEditView, SetupSourceListView, SourceLogListView, + SourceCheckView, SourceCreateView, SourceDeleteView, + SourceEditView, SourceListView, SourceLogView, StagingFileDeleteView, UploadInteractiveVersionView, UploadInteractiveView ) from .wizards import DocumentCreateWizard urlpatterns = [ url( - r'^staging_file/(?P\d+)/(?P.+)/delete/$', - StagingFileDeleteView.as_view(), name='staging_file_delete' + regex=r'^sources/$', name='source_list', + view=SourceListView.as_view() + ), + url( + regex=r'^sources/(?P\w+)/create/$', + name='source_create', view=SourceCreateView.as_view() + ), + url( + regex=r'^sources/(?P\d+)/check/$', name='source_check', + view=SourceCheckView.as_view() + ), + url( + regex=r'^sources/(?P\d+)/delete/$', + name='source_delete', view=SourceDeleteView.as_view() + ), + url( + regex=r'^sources/(?P\d+)/edit/$', name='source_edit', + view=SourceEditView.as_view() + ), + url( + regex=r'^sources/(?P\d+)/logs/$', name='source_logs', + view=SourceLogView.as_view() ), url( - r'^upload/document/new/interactive/(?P\d+)/$', - UploadInteractiveView.as_view(), name='upload_interactive' + regex=r'^sources/(?P\d+)/document/upload/$', + name='upload_interactive', view=UploadInteractiveView.as_view() ), url( - r'^upload/document/new/interactive/$', UploadInteractiveView.as_view(), - name='upload_interactive' + regex=r'^sources/document/upload/$', name='upload_interactive', + view=UploadInteractiveView.as_view() ), url( - r'^upload/document/(?P\d+)/version/interactive/(?P\d+)/$', - UploadInteractiveVersionView.as_view(), name='upload_version' + regex=r'^sources/(?P\d+)/documents/(?P\d+)/versions/upload/$', + name='upload_version', view=UploadInteractiveVersionView.as_view() ), url( - r'^upload/document/(?P\d+)/version/interactive/$', - UploadInteractiveVersionView.as_view(), name='upload_version' - ), - - # Setup views - - url( - r'^setup/list/$', SetupSourceListView.as_view(), - name='setup_source_list' + regex=r'^sources/documents/(?P\d+)/version/upload/$', + name='upload_version', view=UploadInteractiveVersionView.as_view() ), url( - r'^setup/(?P\d+)/edit/$', SetupSourceEditView.as_view(), - name='setup_source_edit' + regex=r'^sources/wizard/$', name='document_create_multiple', + view=DocumentCreateWizard.as_view() ), url( - r'^setup/(?P\d+)/logs/$', SourceLogListView.as_view(), - name='setup_source_logs' - ), - url( - r'^setup/(?P\d+)/delete/$', SetupSourceDeleteView.as_view(), - name='setup_source_delete' - ), - url( - r'^setup/(?P\w+)/create/$', - SetupSourceCreateView.as_view(), name='setup_source_create' - ), - url( - r'^setup/(?P\d+)/check/$', SetupSourceCheckView.as_view(), - name='setup_source_check' - ), - - # Document create views - - url( - r'^create/from/local/multiple/$', DocumentCreateWizard.as_view(), - name='document_create_multiple' + regex=r'^staging_files/(?P\d+)/(?P.+)/delete/$', + name='staging_file_delete', view=StagingFileDeleteView.as_view() ), ] api_urls = [ url( - r'^staging_folders/file/(?P[0-9]+)/(?P.+)/image/$', - APIStagingSourceFileImageView.as_view(), - name='stagingfolderfile-image-view' + regex=r'^staging_folders/file/(?P\d+)/(?P.+)/image/$', + name='stagingfolderfile-image-view', + view=APIStagingSourceFileImageView.as_view() ), url( - r'^staging_folders/file/(?P[0-9]+)/(?P.+)/$', - APIStagingSourceFileView.as_view(), name='stagingfolderfile-detail' + regex=r'^staging_folders/file/(?P\d+)/(?P.+)/$', + name='stagingfolderfile-detail', view=APIStagingSourceFileView.as_view() ), url( - r'^staging_folders/$', APIStagingSourceListView.as_view(), - name='stagingfolder-list' + regex=r'^staging_folders/$', name='stagingfolder-list', + view=APIStagingSourceListView.as_view() ), url( - r'^staging_folders/(?P[0-9]+)/$', APIStagingSourceView.as_view(), - name='stagingfolder-detail' + regex=r'^staging_folders/(?P\d+)/$', + name='stagingfolder-detail', view=APIStagingSourceView.as_view() ) ] diff --git a/mayan/apps/sources/utils.py b/mayan/apps/sources/utils.py index 4bf36c41d2..0a20c9047d 100644 --- a/mayan/apps/sources/utils.py +++ b/mayan/apps/sources/utils.py @@ -14,21 +14,6 @@ from .models import ( ) -def get_class(source_type): - if source_type == SOURCE_CHOICE_WEB_FORM: - return WebFormSource - elif source_type == SOURCE_CHOICE_STAGING: - return StagingFolderSource - elif source_type == SOURCE_CHOICE_WATCH: - return WatchFolderSource - elif source_type == SOURCE_CHOICE_EMAIL_POP3: - return POP3Email - elif source_type == SOURCE_CHOICE_EMAIL_IMAP: - return IMAPEmail - elif source_type == SOURCE_CHOICE_SANE_SCANNER: - return SaneScanner - - def get_form_class(source_type): if source_type == SOURCE_CHOICE_WEB_FORM: return WebFormSetupForm @@ -44,6 +29,21 @@ def get_form_class(source_type): return SaneScannerSetupForm +def get_model(source_type): + if source_type == SOURCE_CHOICE_WEB_FORM: + return WebFormSource + elif source_type == SOURCE_CHOICE_STAGING: + return StagingFolderSource + elif source_type == SOURCE_CHOICE_WATCH: + return WatchFolderSource + elif source_type == SOURCE_CHOICE_EMAIL_POP3: + return POP3Email + elif source_type == SOURCE_CHOICE_EMAIL_IMAP: + return IMAPEmail + elif source_type == SOURCE_CHOICE_SANE_SCANNER: + return SaneScanner + + def get_upload_form_class(source_type): if source_type == SOURCE_CHOICE_WEB_FORM: return WebFormUploadForm diff --git a/mayan/apps/sources/views.py b/mayan/apps/sources/views.py index 54abe63029..a77ca7f398 100644 --- a/mayan/apps/sources/views.py +++ b/mayan/apps/sources/views.py @@ -15,13 +15,12 @@ from django.utils.translation import ugettext_lazy as _ from mayan.apps.acls.models import AccessControlList from mayan.apps.checkouts.models import NewVersionBlock from mayan.apps.common import menu_facet -from mayan.apps.common.mixins import ListModeMixin +from mayan.apps.common.mixins import ExternalObjectMixin, ListModeMixin from mayan.apps.common.models import SharedUploadedFile from mayan.apps.common.views import ( ConfirmView, MultiFormView, SingleObjectCreateView, SingleObjectDeleteView, SingleObjectEditView, SingleObjectListView ) -from mayan.apps.common.widgets import TwoStateWidget from mayan.apps.documents.models import Document, DocumentType from mayan.apps.documents.permissions import ( permission_document_create, permission_document_new_version @@ -34,29 +33,160 @@ from .forms import ( NewDocumentForm, NewVersionForm, WebFormUploadForm, WebFormUploadFormHTML5 ) from .icons import ( - icon_log, icon_setup_sources, icon_staging_folder, icon_upload_view_link + icon_log, icon_source_list, icon_staging_folder, icon_upload_view_link ) from .links import ( - link_setup_source_create_imap_email, link_setup_source_create_pop3_email, - link_setup_source_create_sane_scanner, - link_setup_source_create_staging_folder, - link_setup_source_create_watch_folder, link_setup_source_create_webform + link_source_create_imap_email, link_source_create_pop3_email, + link_source_create_sane_scanner, link_source_create_staging_folder, + link_source_create_watch_folder, link_source_create_webform ) from .literals import SOURCE_UNCOMPRESS_CHOICE_ASK, SOURCE_UNCOMPRESS_CHOICE_Y from .models import InteractiveSource, SaneScanner, Source, StagingFolderSource from .permissions import ( - permission_sources_setup_create, permission_sources_setup_delete, - permission_sources_setup_edit, permission_sources_setup_view, + permission_sources_create, permission_sources_delete, + permission_sources_edit, permission_sources_view, permission_staging_file_delete ) from .tasks import task_check_interval_source, task_source_handle_upload -from .utils import get_class, get_form_class, get_upload_form_class +from .utils import get_form_class, get_model, get_upload_form_class logger = logging.getLogger(__name__) -class SourceLogListView(SingleObjectListView): - view_permission = permission_sources_setup_view +class SourceCheckView(ExternalObjectMixin, ConfirmView): + """ + Trigger the task_check_interval_source task for a given source to + test/debug their configuration irrespective of the schedule task setup. + """ + external_object_pk_url_kwarg = 'source_id' + external_object_queryset = Source.objects.select_subclasses() + external_object_permission = permission_sources_edit + + def get_extra_context(self): + return { + 'object': self.get_object(), + 'subtitle': _( + 'This will execute the source check code even if the source ' + 'is not enabled. Sources that delete content after ' + 'downloading will not do so while being tested. Check the ' + 'source\'s error log for information during testing. A ' + 'successful test will clear the error log.' + ), 'title': _( + 'Trigger check for source "%s"?' + ) % self.get_object(), + } + + def get_object(self): + return self.get_external_object() + + def view_action(self): + task_check_interval_source.apply_async( + kwargs={ + 'source_id': self.get_object().pk, 'test': True + } + ) + + messages.success( + message=_('Source check queued.'), request=self.request + ) + + +class SourceCreateView(SingleObjectCreateView): + post_action_redirect = reverse_lazy(viewname='sources:source_list') + view_permission = permission_sources_create + + def get_form_class(self): + return get_form_class(self.kwargs['source_type']) + + def get_extra_context(self): + return { + 'object': get_model(self.kwargs['source_type']), + 'title': _( + 'Create new source of type: %s' + ) % get_model(self.kwargs['source_type']).class_fullname(), + } + + +class SourceDeleteView(SingleObjectDeleteView): + post_action_redirect = reverse_lazy(viewname='sources:source_list') + view_permission = permission_sources_delete + + def get_object(self): + return get_object_or_404( + klass=Source.objects.select_subclasses(), pk=self.kwargs['source_id'] + ) + + def get_form_class(self): + return get_form_class(self.get_object().source_type) + + def get_extra_context(self): + return { + 'object': self.get_object(), + 'title': _('Delete the source: %s?') % self.get_object(), + } + + +class SourceEditView(SingleObjectEditView): + post_action_redirect = reverse_lazy(viewname='sources:source_list') + view_permission = permission_sources_edit + + def get_object(self): + return get_object_or_404( + klass=Source.objects.select_subclasses(), pk=self.kwargs['source_id'] + ) + + def get_form_class(self): + return get_form_class(self.get_object().source_type) + + def get_extra_context(self): + return { + 'object': self.get_object(), + 'title': _('Edit source: %s') % self.get_object(), + } + + +class SourceListView(SingleObjectListView): + queryset = Source.objects.select_subclasses() + view_permission = permission_sources_view + + def get_extra_context(self): + return { + 'hide_link': True, + 'hide_object': True, + 'no_results_icon': icon_source_list, + 'no_results_secondary_links': [ + link_source_create_webform.resolve( + context=RequestContext(request=self.request) + ), + link_source_create_imap_email.resolve( + context=RequestContext(request=self.request) + ), + link_source_create_pop3_email.resolve( + context=RequestContext(request=self.request) + ), + link_source_create_sane_scanner.resolve( + context=RequestContext(request=self.request) + ), + link_source_create_staging_folder.resolve( + context=RequestContext(request=self.request) + ), + link_source_create_watch_folder.resolve( + context=RequestContext(request=self.request) + ), + ], + 'no_results_text': _( + 'Sources provide the means to upload documents. ' + 'Some sources like the webform, are interactive and require ' + 'user input to operate. Others like the email sources, are ' + 'automatic and run on the background without user intervention.' + ), + 'no_results_title': _('No sources available'), + 'title': _('Sources'), + } + + +class SourceLogView(SingleObjectListView): + view_permission = permission_sources_view def get_extra_context(self): return { @@ -76,7 +206,30 @@ class SourceLogListView(SingleObjectListView): def get_source(self): return get_object_or_404( - klass=Source.objects.select_subclasses(), pk=self.kwargs['pk'] + klass=Source.objects.select_subclasses(), pk=self.kwargs['source_id'] + ) + + +class StagingFileDeleteView(SingleObjectDeleteView): + object_permission = permission_staging_file_delete + object_permission_related = 'staging_folder' + + def get_extra_context(self): + return { + 'object': self.get_object(), + 'object_name': _('Staging file'), + 'source': self.get_source(), + } + + def get_object(self): + source = self.get_source() + return source.get_file( + encoded_filename=self.kwargs['encoded_filename'] + ) + + def get_source(self): + return get_object_or_404( + klass=StagingFolderSource, pk=self.kwargs['staging_folder_id'] ) @@ -122,13 +275,14 @@ class UploadBaseView(ListModeMixin, MultiFormView): if not InteractiveSource.objects.filter(enabled=True).exists(): messages.error( - request, - _( + message=_( 'No interactive document sources have been defined or ' 'none have been enabled, create one before proceeding.' - ) + ), request=request + ) + return HttpResponseRedirect( + redirect_to=reverse(viewname='sources:source_list') ) - return HttpResponseRedirect(reverse('sources:setup_source_list')) return super(UploadBaseView, self).dispatch(request, *args, **kwargs) @@ -142,7 +296,7 @@ class UploadBaseView(ListModeMixin, MultiFormView): try: staging_filelist = list(self.source.get_files()) except Exception as exception: - messages.error(self.request, exception) + messages.error(message=exception, request=self.request) staging_filelist = [] finally: subtemplates_list = [ @@ -243,7 +397,7 @@ class UploadInteractiveView(UploadBaseView): forms['source_form'].cleaned_data ) except SourceException as exception: - messages.error(self.request, exception) + messages.error(message=exception, request=self.request) else: shared_uploaded_file = SharedUploadedFile.objects.create( file=uploaded_file.file @@ -257,7 +411,7 @@ class UploadInteractiveView(UploadBaseView): try: self.source.clean_up_upload_file(uploaded_file) except Exception as exception: - messages.error(self.request, exception) + messages.error(message=exception, request=self.request) querystring = furl() querystring.args.update(self.request.GET) @@ -293,15 +447,14 @@ class UploadInteractiveView(UploadBaseView): raise type(exception)(message) else: messages.success( - self.request, - _( + message=_( 'New document queued for upload and will be available ' 'shortly.' - ) + ), request=self.request ) return HttpResponseRedirect( - '{}?{}'.format( + redirect_to='{}?{}'.format( reverse( self.request.resolver_match.view_name, kwargs=self.request.resolver_match.kwargs @@ -372,19 +525,19 @@ class UploadInteractiveVersionView(UploadBaseView): self.subtemplates_list = [] - self.document = get_object_or_404(klass=Document, pk=kwargs['document_pk']) + self.document = get_object_or_404(klass=Document, pk=kwargs['document_id']) # TODO: Try to remove this new version block check from here if NewVersionBlock.objects.is_blocked(self.document): messages.error( - self.request, - _( + message=_( 'Document "%s" is blocked from uploading new versions.' - ) % self.document + ) % self.document, request=self.request ) return HttpResponseRedirect( - reverse( - 'documents:document_version_list', args=(self.document.pk,) + redirect_to=reverse( + viewname='documents:document_version_list', + kwargs={'document_version_id': self.document.pk} ) ) @@ -405,7 +558,7 @@ class UploadInteractiveVersionView(UploadBaseView): forms['source_form'].cleaned_data ) except SourceException as exception: - messages.error(self.request, exception) + messages.error(message=exception, request=self.request) else: shared_uploaded_file = SharedUploadedFile.objects.create( file=uploaded_file.file @@ -414,7 +567,7 @@ class UploadInteractiveVersionView(UploadBaseView): try: self.source.clean_up_upload_file(uploaded_file) except Exception as exception: - messages.error(self.request, exception) + messages.error(message=exception, request=self.request) if not self.request.user.is_anonymous: user_id = self.request.user.pk @@ -429,16 +582,16 @@ class UploadInteractiveVersionView(UploadBaseView): )) messages.success( - self.request, - _( + message=_( 'New document version queued for upload and will be ' 'available shortly.' - ) + ), request=self.request ) return HttpResponseRedirect( - reverse( - 'documents:document_version_list', args=(self.document.pk,) + redirect_to=reverse( + viewname='documents:document_version_list', + kwargs={'document_id': self.document.pk} ) ) @@ -474,155 +627,3 @@ class UploadInteractiveVersionView(UploadBaseView): ) % self.source.label return context - - -class StagingFileDeleteView(SingleObjectDeleteView): - object_permission = permission_staging_file_delete - object_permission_related = 'staging_folder' - - def get_extra_context(self): - return { - 'object': self.get_object(), - 'object_name': _('Staging file'), - 'source': self.get_source(), - } - - def get_object(self): - source = self.get_source() - return source.get_file( - encoded_filename=self.kwargs['encoded_filename'] - ) - - def get_source(self): - return get_object_or_404( - klass=StagingFolderSource, pk=self.kwargs['pk'] - ) - - -# Setup views -class SetupSourceCheckView(ConfirmView): - """ - Trigger the task_check_interval_source task for a given source to - test/debug their configuration irrespective of the schedule task setup. - """ - view_permission = permission_sources_setup_create - - def get_extra_context(self): - return { - 'object': self.get_object(), - 'subtitle': _( - 'This will execute the source check code even if the source ' - 'is not enabled. Sources that delete content after ' - 'downloading will not do so while being tested. Check the ' - 'source\'s error log for information during testing. A ' - 'successful test will clear the error log.' - ), 'title': _( - 'Trigger check for source "%s"?' - ) % self.get_object(), - } - - def get_object(self): - return get_object_or_404(klass=Source.objects.select_subclasses(), pk=self.kwargs['pk']) - - def view_action(self): - task_check_interval_source.apply_async( - kwargs={ - 'source_id': self.get_object().pk, 'test': True - } - ) - - messages.success(self.request, _('Source check queued.')) - - -class SetupSourceCreateView(SingleObjectCreateView): - post_action_redirect = reverse_lazy('sources:setup_source_list') - view_permission = permission_sources_setup_create - - def get_form_class(self): - return get_form_class(self.kwargs['source_type']) - - def get_extra_context(self): - return { - 'object': self.kwargs['source_type'], - 'title': _( - 'Create new source of type: %s' - ) % get_class(self.kwargs['source_type']).class_fullname(), - } - - -class SetupSourceDeleteView(SingleObjectDeleteView): - post_action_redirect = reverse_lazy('sources:setup_source_list') - view_permission = permission_sources_setup_delete - - def get_object(self): - return get_object_or_404( - klass=Source.objects.select_subclasses(), pk=self.kwargs['pk'] - ) - - def get_form_class(self): - return get_form_class(self.get_object().source_type) - - def get_extra_context(self): - return { - 'object': self.get_object(), - 'title': _('Delete the source: %s?') % self.get_object(), - } - - -class SetupSourceEditView(SingleObjectEditView): - post_action_redirect = reverse_lazy('sources:setup_source_list') - view_permission = permission_sources_setup_edit - - def get_object(self): - return get_object_or_404( - klass=Source.objects.select_subclasses(), pk=self.kwargs['pk'] - ) - - def get_form_class(self): - return get_form_class(self.get_object().source_type) - - def get_extra_context(self): - return { - 'object': self.get_object(), - 'title': _('Edit source: %s') % self.get_object(), - } - - -class SetupSourceListView(SingleObjectListView): - queryset = Source.objects.select_subclasses() - view_permission = permission_sources_setup_view - - def get_extra_context(self): - return { - 'hide_link': True, - 'hide_object': True, - 'no_results_icon': icon_setup_sources, - 'no_results_secondary_links': [ - link_setup_source_create_webform.resolve( - context=RequestContext(request=self.request) - ), - link_setup_source_create_imap_email.resolve( - context=RequestContext(request=self.request) - ), - link_setup_source_create_pop3_email.resolve( - context=RequestContext(request=self.request) - ), - link_setup_source_create_sane_scanner.resolve( - context=RequestContext(request=self.request) - ), - link_setup_source_create_staging_folder.resolve( - context=RequestContext(request=self.request) - ), - link_setup_source_create_watch_folder.resolve( - context=RequestContext(request=self.request) - ), - ], - 'no_results_text': _( - 'Sources provide the means to upload documents. ' - 'Some sources like the webform, are interactive and require ' - 'user input to operate. Others like the email sources, are ' - 'automatic and run on the background without user intervention.' - ), - 'no_results_title': _('No sources available'), - 'title': _('Sources'), - } diff --git a/mayan/apps/sources/wizards.py b/mayan/apps/sources/wizards.py index 8b8f7549b8..b279b16029 100644 --- a/mayan/apps/sources/wizards.py +++ b/mayan/apps/sources/wizards.py @@ -150,12 +150,25 @@ class DocumentCreateWizard(SessionWizardView): 'none have been enabled, create one before proceeding.' ) ) - return HttpResponseRedirect(reverse('sources:setup_source_list')) + return HttpResponseRedirect(reverse(viewname='sources:setup_source_list')) return super( DocumentCreateWizard, self ).dispatch(request, *args, **kwargs) + def done(self, form_list, **kwargs): + query_dict = {} + + for step in WizardStep.get_all(): + query_dict.update(step.done(wizard=self) or {}) + + url = furl(reverse(viewname='sources:upload_interactive')) + # Use equal and not .update() to get the same result as using + # urlencode(doseq=True) + url.args = query_dict + + return HttpResponseRedirect(url) + def get_context_data(self, form, **kwargs): context = super( DocumentCreateWizard, self @@ -184,16 +197,3 @@ class DocumentCreateWizard(SessionWizardView): def get_form_kwargs(self, step): return WizardStep.get(name=step).get_form_kwargs(wizard=self) or {} - - def done(self, form_list, **kwargs): - query_dict = {} - - for step in WizardStep.get_all(): - query_dict.update(step.done(wizard=self) or {}) - - url = furl(reverse('sources:upload_interactive')) - # Use equal and not .update() to get the same result as using - # urlencode(doseq=True) - url.args = query_dict - - return HttpResponseRedirect(url)