Files
mayan-edms/mayan/apps/sources/views.py
Roberto Rosario 8e69178e07 Project: Switch to full app paths
Instead of inserting the path of the apps into the Python app,
the apps are now referenced by their full import path.

This app name claves with external or native Python libraries.
Example: Mayan statistics app vs. Python new statistics library.

Every app reference is now prepended with 'mayan.apps'.

Existing config.yml files need to be updated manually.

Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
2018-12-05 02:04:20 -04:00

629 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 mayan.apps.acls.models import AccessControlList
from mayan.apps.checkouts.models import NewVersionBlock
from mayan.apps.common import menu_facet
from mayan.apps.common.models import SharedUploadedFile
from mayan.apps.common.utils import encapsulate
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 DocumentType, Document
from mayan.apps.documents.permissions import (
permission_document_create, permission_document_new_version
)
from mayan.apps.documents.tasks import task_upload_new_version
from mayan.apps.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_css_classes': '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_view
def get_extra_context(self):
return {
'object': self.get_object(),
'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
}
)
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'),
}