Files
mayan-edms/mayan/apps/sources/views.py
Roberto Rosario 382173351a Source: Change source test behavior
Update sourcs to accept a test argument to their check methods.
This is to allow for explicit test behavior like running the
check method code even when the source is disabled and to
not deleted downloaded content during a test.

Signed-off-by: Roberto Rosario <Roberto.Rosario@mayan-edms.com>
2019-04-05 01:05:58 -04:00

637 lines
22 KiB
Python

from __future__ import absolute_import, unicode_literals
import logging
from furl import furl
from django.contrib import messages
from django.http import HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404
from django.template import RequestContext
from django.urls import reverse, reverse_lazy
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from acls.models import AccessControlList
from checkouts.models import NewVersionBlock
from common import menu_facet
from common.models import SharedUploadedFile
from common.utils import encapsulate
from common.views import (
ConfirmView, MultiFormView, SingleObjectCreateView,
SingleObjectDeleteView, SingleObjectEditView, SingleObjectListView
)
from common.widgets import TwoStateWidget
from documents.models import DocumentType, Document
from documents.permissions import (
permission_document_create, permission_document_new_version
)
from documents.tasks import task_upload_new_version
from navigation import Link
from .exceptions import SourceException
from .forms import (
NewDocumentForm, NewVersionForm, WebFormUploadForm, WebFormUploadFormHTML5
)
from .icons import icon_log, icon_setup_sources, icon_upload_view_link
from .literals import SOURCE_UNCOMPRESS_CHOICE_ASK, SOURCE_UNCOMPRESS_CHOICE_Y
from .links import (
link_setup_source_create_imap_email, link_setup_source_create_pop3_email,
link_setup_source_create_staging_folder,
link_setup_source_create_watch_folder, link_setup_source_create_webform,
link_setup_source_create_sane_scanner
)
from .models import (
InteractiveSource, Source, SaneScanner, StagingFolderSource
)
from .permissions import (
permission_sources_setup_create, permission_sources_setup_delete,
permission_sources_setup_edit, permission_sources_setup_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
logger = logging.getLogger(__name__)
class SourceLogListView(SingleObjectListView):
view_permission = permission_sources_setup_view
def get_extra_context(self):
return {
'hide_object': True,
'no_results_icon': icon_log,
'no_results_text': _(
'Any error produced during the usage of a source will be '
'listed here to assist in debugging.'
),
'no_results_title': _('No log entries available'),
'object': self.get_source(),
'title': _('Log entries for source: %s') % self.get_source(),
}
def get_object_list(self):
return self.get_source().logs.all()
def get_source(self):
return get_object_or_404(
Source.objects.select_subclasses(), pk=self.kwargs['pk']
)
class UploadBaseView(MultiFormView):
template_name = 'appearance/generic_form.html'
prefixes = {'source_form': 'source', 'document_form': 'document'}
@staticmethod
def get_tab_link_for_source(source, document=None):
if document:
view = 'sources:upload_version'
args = ('"{}"'.format(document.pk), '"{}"'.format(source.pk),)
else:
view = 'sources:upload_interactive'
args = ('"{}"'.format(source.pk),)
return Link(
args=args,
icon_class=icon_upload_view_link,
keep_query=True,
remove_from_query=['page'],
text=source.label,
view=view,
)
@staticmethod
def get_active_tab_links(document=None):
return [
UploadBaseView.get_tab_link_for_source(source, document)
for source in InteractiveSource.objects.filter(enabled=True).select_subclasses()
]
def dispatch(self, request, *args, **kwargs):
if 'source_id' in kwargs:
self.source = get_object_or_404(
Source.objects.filter(enabled=True).select_subclasses(),
pk=kwargs['source_id']
)
else:
self.source = InteractiveSource.objects.filter(
enabled=True
).select_subclasses().first()
if not InteractiveSource.objects.filter(enabled=True).exists():
messages.error(
request,
_(
'No interactive document sources have been defined or '
'none have been enabled, create one before proceeding.'
)
)
return HttpResponseRedirect(reverse('sources:setup_source_list'))
return super(UploadBaseView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(UploadBaseView, self).get_context_data(**kwargs)
subtemplates_list = []
context['source'] = self.source
if isinstance(self.source, StagingFolderSource):
try:
staging_filelist = list(self.source.get_files())
except Exception as exception:
messages.error(self.request, exception)
staging_filelist = []
finally:
subtemplates_list = [
{
'name': 'appearance/generic_multiform_subtemplate.html',
'context': {
'forms': context['forms'],
'title': _('Document properties'),
}
},
{
'name': 'appearance/generic_list_subtemplate.html',
'context': {
'hide_link': True,
'object_list': staging_filelist,
'title': _('Files in staging path'),
}
},
]
elif isinstance(self.source, SaneScanner):
subtemplates_list.append({
'name': 'sources/upload_multiform_subtemplate.html',
'context': {
'forms': context['forms'],
'is_multipart': True,
'title': _('Document properties'),
'submit_label': _('Scan'),
},
})
else:
subtemplates_list.append({
'name': 'sources/upload_multiform_subtemplate.html',
'context': {
'forms': context['forms'],
'is_multipart': True,
'title': _('Document properties'),
},
})
menu_facet.bound_links['sources:upload_interactive'] = self.tab_links
menu_facet.bound_links['sources:upload_version'] = self.tab_links
context.update({
'subtemplates_list': subtemplates_list,
})
return context
class UploadInteractiveView(UploadBaseView):
def dispatch(self, request, *args, **kwargs):
self.subtemplates_list = []
self.document_type = get_object_or_404(
DocumentType,
pk=self.request.GET.get(
'document_type_id', self.request.POST.get('document_type_id')
)
)
AccessControlList.objects.check_access(
permissions=permission_document_create, user=request.user,
obj=self.document_type
)
self.tab_links = UploadBaseView.get_active_tab_links()
try:
return super(
UploadInteractiveView, self
).dispatch(request, *args, **kwargs)
except Exception as exception:
if request.is_ajax():
return JsonResponse(
data={'error': force_text(exception)}, status=500
)
else:
raise
def forms_valid(self, forms):
if self.source.can_compress:
if self.source.uncompress == SOURCE_UNCOMPRESS_CHOICE_ASK:
expand = forms['source_form'].cleaned_data.get('expand')
else:
if self.source.uncompress == SOURCE_UNCOMPRESS_CHOICE_Y:
expand = True
else:
expand = False
else:
expand = False
try:
uploaded_file = self.source.get_upload_file_object(
forms['source_form'].cleaned_data
)
except SourceException as exception:
messages.error(self.request, exception)
else:
shared_uploaded_file = SharedUploadedFile.objects.create(
file=uploaded_file.file
)
if not self.request.user.is_anonymous:
user_id = self.request.user.pk
else:
user_id = None
try:
self.source.clean_up_upload_file(uploaded_file)
except Exception as exception:
messages.error(self.request, exception)
querystring = furl()
querystring.args.update(self.request.GET)
querystring.args.update(self.request.POST)
try:
task_source_handle_upload.apply_async(
kwargs=dict(
description=forms['document_form'].cleaned_data.get('description'),
document_type_id=self.document_type.pk,
expand=expand,
label=forms['document_form'].get_final_label(
filename=force_text(shared_uploaded_file)
),
language=forms['document_form'].cleaned_data.get('language'),
querystring=querystring.tostr(),
shared_uploaded_file_id=shared_uploaded_file.pk,
source_id=self.source.pk,
user_id=user_id,
)
)
except Exception as exception:
message = _(
'Error executing document upload task; '
'%(exception)s, %(exception_class)s'
) % {
'exception': exception,
'exception_class': type(exception),
}
logger.critical(
message, exc_info=True
)
raise type(exception)(message)
else:
messages.success(
self.request,
_(
'New document queued for upload and will be available '
'shortly.'
)
)
return HttpResponseRedirect(
'{}?{}'.format(
reverse(
self.request.resolver_match.view_name,
kwargs=self.request.resolver_match.kwargs
), self.request.META['QUERY_STRING']
),
)
def create_source_form_form(self, **kwargs):
if hasattr(self.source, 'uncompress'):
show_expand = self.source.uncompress == SOURCE_UNCOMPRESS_CHOICE_ASK
else:
show_expand = False
return self.get_form_classes()['source_form'](
prefix=kwargs['prefix'],
source=self.source,
show_expand=show_expand,
data=kwargs.get('data', None),
files=kwargs.get('files', None),
)
def create_document_form_form(self, **kwargs):
return self.get_form_classes()['document_form'](
prefix=kwargs['prefix'],
document_type=self.document_type,
data=kwargs.get('data', None),
files=kwargs.get('files', None),
)
def get_form_classes(self):
source_form_class = get_upload_form_class(self.source.source_type)
# Override source form class to enable the HTML5 file uploader
if source_form_class == WebFormUploadForm:
source_form_class = WebFormUploadFormHTML5
return {
'document_form': NewDocumentForm,
'source_form': source_form_class
}
def get_context_data(self, **kwargs):
context = super(UploadInteractiveView, self).get_context_data(**kwargs)
context['title'] = _(
'Upload a document of type "%(document_type)s" from '
'source: %(source)s'
) % {'document_type': self.document_type, 'source': self.source.label}
if not isinstance(self.source, StagingFolderSource) and not isinstance(self.source, SaneScanner):
context['subtemplates_list'][0]['context'].update(
{
'form_action': '{}?{}'.format(
reverse(
self.request.resolver_match.view_name,
kwargs=self.request.resolver_match.kwargs
), self.request.META['QUERY_STRING']
),
'form_class': 'dropzone',
'form_disable_submit': True,
'form_id': 'html5upload',
}
)
return context
class UploadInteractiveVersionView(UploadBaseView):
def dispatch(self, request, *args, **kwargs):
self.subtemplates_list = []
self.document = get_object_or_404(Document, pk=kwargs['document_pk'])
# TODO: Try to remove this new version block check from here
if NewVersionBlock.objects.is_blocked(self.document):
messages.error(
self.request,
_(
'Document "%s" is blocked from uploading new versions.'
) % self.document
)
return HttpResponseRedirect(
reverse(
'documents:document_version_list', args=(self.document.pk,)
)
)
AccessControlList.objects.check_access(
permissions=permission_document_new_version,
user=self.request.user, obj=self.document
)
self.tab_links = UploadBaseView.get_active_tab_links(self.document)
return super(
UploadInteractiveVersionView, self
).dispatch(request, *args, **kwargs)
def forms_valid(self, forms):
try:
uploaded_file = self.source.get_upload_file_object(
forms['source_form'].cleaned_data
)
except SourceException as exception:
messages.error(self.request, exception)
else:
shared_uploaded_file = SharedUploadedFile.objects.create(
file=uploaded_file.file
)
try:
self.source.clean_up_upload_file(uploaded_file)
except Exception as exception:
messages.error(self.request, exception)
if not self.request.user.is_anonymous:
user_id = self.request.user.pk
else:
user_id = None
task_upload_new_version.apply_async(kwargs=dict(
shared_uploaded_file_id=shared_uploaded_file.pk,
document_id=self.document.pk,
user_id=user_id,
comment=forms['document_form'].cleaned_data.get('comment')
))
messages.success(
self.request,
_(
'New document version queued for upload and will be '
'available shortly.'
)
)
return HttpResponseRedirect(
reverse(
'documents:document_version_list', args=(self.document.pk,)
)
)
def create_source_form_form(self, **kwargs):
return self.get_form_classes()['source_form'](
prefix=kwargs['prefix'],
source=self.source,
show_expand=False,
data=kwargs.get('data', None),
files=kwargs.get('files', None),
)
def create_document_form_form(self, **kwargs):
return self.get_form_classes()['document_form'](
prefix=kwargs['prefix'],
data=kwargs.get('data', None),
files=kwargs.get('files', None),
)
def get_form_classes(self):
return {
'document_form': NewVersionForm,
'source_form': get_upload_form_class(self.source.source_type)
}
def get_context_data(self, **kwargs):
context = super(
UploadInteractiveVersionView, self
).get_context_data(**kwargs)
context['object'] = self.document
context['title'] = _(
'Upload a new version from source: %s'
) % 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(
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(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(
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(
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 {
'extra_columns': (
{
'name': _('Type'),
'attribute': encapsulate(lambda entry: entry.class_fullname())
},
{
'name': _('Enabled'),
'attribute': encapsulate(
lambda entry: TwoStateWidget(state=entry.enabled).render()
)
},
),
'hide_link': 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'),
}