Add MultiFormView class based view, convert InteractiveUpload view to MultiForm view

This commit is contained in:
Roberto Rosario
2014-10-24 18:18:40 -04:00
parent 4acf88c9a6
commit c23205f464
8 changed files with 271 additions and 182 deletions

View File

@@ -14,6 +14,7 @@ from django.shortcuts import redirect, render_to_response
from django.template import RequestContext from django.template import RequestContext
from django.utils.http import urlencode from django.utils.http import urlencode
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import FormView
from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.views.generic.list import ListView from django.views.generic.list import ListView
@@ -394,3 +395,73 @@ class SingleObjectListView(MayanPermissionCheckMixin, ExtraContextMixin, MayanVi
return queryset return queryset
else: else:
return queryset return queryset
class MultiFormView(FormView):
prefixes = {}
prefix = None
def get_form_kwargs(self, form_name):
kwargs = {}
kwargs.update({'initial': self.get_initial(form_name)})
kwargs.update({'prefix': self.get_prefix(form_name)})
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
return kwargs
def _create_form(self, form_name, klass):
form_kwargs = self.get_form_kwargs(form_name)
form_create_method = 'create_%s_form' % form_name
if hasattr(self, form_create_method):
form = getattr(self, form_create_method)(**form_kwargs)
else:
form = klass(**form_kwargs)
return form
def get_forms(self, form_classes):
return dict([(key, self._create_form(key, klass)) for key, klass in form_classes.items()])
def get_initial(self, form_name):
initial_method = 'get_%s_initial' % form_name
if hasattr(self, initial_method):
return getattr(self, initial_method)()
else:
return self.initial.copy()
def get_prefix(self, form_name):
return self.prefixes.get(form_name, self.prefix)
def get(self, request, *args, **kwargs):
form_classes = self.get_form_classes()
forms = self.get_forms(form_classes)
return self.render_to_response(self.get_context_data(forms=forms))
def forms_valid(self, forms):
for form_name, form in forms.items():
form_valid_method = '%s_form_valid' % form_name
if hasattr(self, form_valid_method):
return getattr(self, form_valid_method)(form)
self.all_forms_valid(forms)
return HttpResponseRedirect(self.get_success_url())
def forms_invalid(self, forms):
return self.render_to_response(self.get_context_data(forms=forms))
def post(self, request, *args, **kwargs):
form_classes = self.get_form_classes()
form_name = request.POST.get('action')
forms = self.get_forms(form_classes)
if all([form.is_valid() for form in forms.values()]):
return self.forms_valid(forms)
else:
return self.forms_invalid(forms)

View File

@@ -11,7 +11,7 @@
<form method="{{ submit_method|default:'post' }}" action="{{ form_action }}" class="form"> <form method="{{ submit_method|default:'post' }}" action="{{ form_action }}" class="form">
{% endif %} {% endif %}
{% for form in forms %} {% for form_name, form in forms.items %}
{% if submit_method != 'GET' and submit_method != 'get' %} {% if submit_method != 'GET' and submit_method != 'get' %}
{% csrf_token %} {% csrf_token %}
{% endif %} {% endif %}

View File

@@ -24,6 +24,13 @@ class PseudoFile(File):
self.file.seek(0) self.file.seek(0)
class SourceUploadedFile(File):
def __init__(self, source, file, extra_data=None):
self.file = file
self.source = source
self.extra_data = extra_data
class Attachment(File): class Attachment(File):
def __init__(self, part, name): def __init__(self, part, name):
self.name = name self.name = name

View File

@@ -23,6 +23,7 @@ class UploadBaseForm(forms.Form):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
show_expand = kwargs.pop('show_expand', False) show_expand = kwargs.pop('show_expand', False)
self.source = kwargs.pop('source') self.source = kwargs.pop('source')
super(UploadBaseForm, self).__init__(*args, **kwargs) super(UploadBaseForm, self).__init__(*args, **kwargs)
if show_expand: if show_expand:

View File

@@ -24,7 +24,7 @@ from djcelery.models import PeriodicTask, IntervalSchedule
from documents.models import Document, DocumentType from documents.models import Document, DocumentType
from metadata.api import save_metadata_list from metadata.api import save_metadata_list
from .classes import Attachment, StagingFile from .classes import Attachment, SourceUploadedFile, StagingFile
from .literals import (DEFAULT_INTERVAL, DEFAULT_POP3_TIMEOUT, from .literals import (DEFAULT_INTERVAL, DEFAULT_POP3_TIMEOUT,
DEFAULT_IMAP_MAILBOX, SOURCE_CHOICES, DEFAULT_IMAP_MAILBOX, SOURCE_CHOICES,
SOURCE_CHOICE_STAGING, SOURCE_CHOICE_WATCH, SOURCE_CHOICE_STAGING, SOURCE_CHOICE_WATCH,
@@ -53,9 +53,6 @@ class Source(models.Model):
def fullname(self): def fullname(self):
return u' '.join([self.class_fullname(), '"%s"' % self.title]) return u' '.join([self.class_fullname(), '"%s"' % self.title])
def internal_name(self):
return u'%s_%d' % (self.source_type, self.pk)
def get_transformation_list(self): def get_transformation_list(self):
return SourceTransformation.transformations.get_for_object_as_list(self) return SourceTransformation.transformations.get_for_object_as_list(self)
@@ -93,7 +90,7 @@ class Source(models.Model):
new_version.apply_default_transformations(transformations) new_version.apply_default_transformations(transformations)
if metadata_dict_list: if metadata_dict_list:
save_metadata_list(metadata_dict_list, document, create=True) save_metadata_list(metadata_dict_list, new_version.document, create=True)
def upload_new_version(self, file_object, document, user, new_version_data=None, comment=None): def upload_new_version(self, file_object, document, user, new_version_data=None, comment=None):
if not new_version_data: if not new_version_data:
@@ -102,6 +99,12 @@ class Source(models.Model):
# TODO: new HISTORY for version updates # TODO: new HISTORY for version updates
new_version = document.new_version(file=file_object, user=user, **new_version_data) new_version = document.new_version(file=file_object, user=user, **new_version_data)
def get_upload_file_object(self, request, form):
pass
def clean_up_upload_file(self, upload_file_object):
pass
class Meta: class Meta:
ordering = ('title',) ordering = ('title',)
verbose_name = _(u'Source') verbose_name = _(u'Source')
@@ -144,6 +147,17 @@ class StagingFolderSource(InteractiveSource):
except OSError as exception: except OSError as exception:
raise Exception(_(u'Unable get list of staging files: %s') % exception) raise Exception(_(u'Unable get list of staging files: %s') % exception)
def get_upload_file_object(self, request, form):
staging_file = self.get_file(encoded_filename=form.cleaned_data['staging_file_id'])
return SourceUploadedFile(source=self, file=staging_file.as_file(), extra_data=staging_file)
def clean_up_upload_file(self, upload_file_object):
if self.delete_after_upload:
try:
upload_file_object.extra_data.delete()
except Exception as exception:
raise Exception(_(u'Error deleting staging file; %s') % exception)
class Meta: class Meta:
verbose_name = _(u'Staging folder') verbose_name = _(u'Staging folder')
verbose_name_plural = _(u'Staging folders') verbose_name_plural = _(u'Staging folders')
@@ -153,9 +167,13 @@ class WebFormSource(InteractiveSource):
is_interactive = True is_interactive = True
source_type = SOURCE_CHOICE_WEB_FORM source_type = SOURCE_CHOICE_WEB_FORM
# TODO: unify uncompress as an InteractiveSource field
uncompress = models.CharField(max_length=1, choices=SOURCE_INTERACTIVE_UNCOMPRESS_CHOICES, verbose_name=_(u'Uncompress'), help_text=_(u'Whether to expand or not compressed archives.')) uncompress = models.CharField(max_length=1, choices=SOURCE_INTERACTIVE_UNCOMPRESS_CHOICES, verbose_name=_(u'Uncompress'), help_text=_(u'Whether to expand or not compressed archives.'))
# Default path # Default path
def get_upload_file_object(self, request, form):
return SourceUploadedFile(source=self, file=form.cleaned_data['file'])
class Meta: class Meta:
verbose_name = _(u'Web form') verbose_name = _(u'Web form')
verbose_name_plural = _(u'Web forms') verbose_name_plural = _(u'Web forms')

View File

@@ -21,7 +21,7 @@ def task_check_interval_source(source_id):
@app.task(ignore_result=True) @app.task(ignore_result=True)
def task_upload_document(source_id, file_path, label, document_type_id=None, expand=False, metadata_dict_list=None, user_id=None, command_line=False, description=None, language=None): def task_upload_document(source_id, file_path, label, document_type_id, expand=False, metadata_dict_list=None, user_id=None, command_line=False, description=None, language=None):
source = Source.objects.get_subclass(pk=source_id) source = Source.objects.get_subclass(pk=source_id)
document_type = DocumentType.objects.get(pk=document_type_id) document_type = DocumentType.objects.get(pk=document_type_id)

View File

@@ -5,13 +5,14 @@ from django.conf.urls import patterns, url
from .api_views import (APIDocumentCreateView, APIStagingSourceFileView, from .api_views import (APIDocumentCreateView, APIStagingSourceFileView,
APIStagingSourceFileImageView, APIStagingSourceListView, APIStagingSourceFileImageView, APIStagingSourceListView,
APIStagingSourceView) APIStagingSourceView)
from .views import UploadInteractiveView
from .wizards import DocumentCreateWizard from .wizards import DocumentCreateWizard
urlpatterns = patterns('sources.views', urlpatterns = patterns('sources.views',
url(r'^staging_file/(?P<staging_folder_pk>\d+)/(?P<encoded_filename>.+)/delete/$', 'staging_file_delete', name='staging_file_delete'), url(r'^staging_file/(?P<staging_folder_pk>\d+)/(?P<encoded_filename>.+)/delete/$', 'staging_file_delete', name='staging_file_delete'),
url(r'^upload/document/new/interactive/(?P<source_id>\d+)/$', 'upload_interactive', (), 'upload_interactive'), url(r'^upload/document/new/interactive/(?P<source_id>\d+)/$', UploadInteractiveView.as_view(), name='upload_interactive'),
url(r'^upload/document/new/interactive/$', 'upload_interactive', (), 'upload_interactive'), url(r'^upload/document/new/interactive/$', UploadInteractiveView.as_view(), name='upload_interactive'),
url(r'^upload/document/(?P<document_pk>\d+)/version/interactive/(?P<source_id>\d+)/$', 'upload_new_version', (), 'upload_version'), url(r'^upload/document/(?P<document_pk>\d+)/version/interactive/(?P<source_id>\d+)/$', 'upload_new_version', (), 'upload_version'),
url(r'^upload/document/(?P<document_pk>\d+)/version/interactive/$', 'upload_new_version', (), 'upload_version'), url(r'^upload/document/(?P<document_pk>\d+)/version/interactive/$', 'upload_new_version', (), 'upload_version'),

View File

@@ -5,7 +5,7 @@ import tempfile
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse, reverse_lazy
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response, get_object_or_404 from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext from django.template import RequestContext
@@ -16,6 +16,7 @@ from django.utils.translation import ugettext_lazy as _
from acls.models import AccessEntry from acls.models import AccessEntry
from common.utils import encapsulate from common.utils import encapsulate
from common.views import MultiFormView
from documents.exceptions import NewDocumentVersionNotAllowed from documents.exceptions import NewDocumentVersionNotAllowed
from documents.models import DocumentType, Document from documents.models import DocumentType, Document
from documents.permissions import (PERMISSION_DOCUMENT_CREATE, from documents.permissions import (PERMISSION_DOCUMENT_CREATE,
@@ -52,7 +53,7 @@ def document_create_siblings(request, document_id):
return HttpResponseRedirect('%s?%s' % (url, urlencode(query_dict))) return HttpResponseRedirect('%s?%s' % (url, urlencode(query_dict)))
def return_function(obj): def conditional_highlight_factory(obj):
return lambda context: context['source'].source_type == obj.source_type and context['source'].pk == obj.pk return lambda context: context['source'].source_type == obj.source_type and context['source'].pk == obj.pk
@@ -69,7 +70,7 @@ def get_tab_link_for_source(source, document=None):
'view': view, 'view': view,
'args': args, 'args': args,
'keep_query': True, 'keep_query': True,
'conditional_highlight': return_function(source), 'conditional_highlight': conditional_highlight_factory(source),
} }
@@ -91,146 +92,116 @@ def get_active_tab_links(document=None):
} }
def upload_new_version(request, document_id): class UploadInteractiveView(MultiFormView):
document = get_object_or_404(Document, pk=document_pk) template_name = 'main/generic_form.html'
# try: prefixes = {'source_form': 'source', 'document_form': 'document'}
# Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_NEW_VERSION])
# except PermissionDenied:
# AccessEntry.objects.check_access(PERMISSION_DOCUMENT_NEW_VERSION, request.user, document)
# results = get_active_tab_links(document)
# messages.success(request, _(u'New document version queued for uploaded and will be available shortly.')) def dispatch(self, request, *args, **kwargs):
# return HttpResponseRedirect(reverse('documents:document_version_list', args=[document.pk])) self.subtemplates_list = []
# title = _(u'Upload a new version from source: %s') % source.title Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_CREATE])
# TODO: move to version upload
#if document:
#context['object'] = document
pass
self.document_type = get_object_or_404(DocumentType, pk=self.request.GET['document_type_id'])
def upload_interactive(request, source_id=None): if 'source_id' in kwargs:
subtemplates_list = [] self.source = get_object_or_404(Source.objects.filter(enabled=True).select_subclasses(), pk=kwargs['source_id'])
Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_CREATE])
results = get_active_tab_links()
context = {}
if InteractiveSource.objects.filter(enabled=True).count() == 0:
subtemplates_list.append(
{
'name': 'main/generic_subtemplate.html',
'context': {
'title': _(u'Upload sources'),
'paragraphs': [
_(u'No interactive document sources have been defined or none have been enabled.'),
],
}
})
document_type = get_object_or_404(DocumentType, pk=request.GET['document_type_id'])
if source_id:
source = get_object_or_404(Source.objects.filter(enabled=True).select_subclasses(), pk=source_id)
else:
source = InteractiveSource.objects.filter(enabled=True).select_subclasses().first()
if source:
upload_form_class = get_upload_form_class(source.source_type)
context['source'] = source
if request.method == 'POST':
upload_form = upload_form_class(
request.POST, request.FILES,
show_expand=(source.uncompress == SOURCE_UNCOMPRESS_CHOICE_ASK),
source=source,
prefix='source'
)
document_form = NewDocumentForm(
data=request.POST,
document_type=document_type,
prefix='document')
if upload_form.is_valid() and document_form.is_valid():
try:
if source.uncompress == SOURCE_UNCOMPRESS_CHOICE_ASK:
expand = upload_form.cleaned_data.get('expand')
else:
if source.uncompress == SOURCE_UNCOMPRESS_CHOICE_Y:
expand = True
else:
expand = False
# TODO: move this to model: "get_file_object(request, upload_form)"
if isinstance(source, WebFormSource):
staging_file = None
file_object = request.FILES['source-file']
else:
staging_file = source.get_file(encoded_filename=upload_form.cleaned_data['staging_file_id'])
file_object = staging_file.as_file()
temporary_file = tempfile.NamedTemporaryFile(delete=False)
for chunk in file_object.chunks():
temporary_file.write(chunk)
temporary_file.close()
file_object.close()
# TODO: move this to model: "post_upload(request)"
if isinstance(source, StagingFolderSource):
if source.delete_after_upload:
try:
staging_file.delete()
except Exception as exception:
messages.error(request, _(u'Error deleting staging file; %s') % exception)
if not request.user.is_anonymous():
user_id = request.user.pk
else:
user_id = None
# Determine how to name the new document
label = file_object.name
if 'document_type_available_filenames' in document_form.cleaned_data:
if document_form.cleaned_data['document_type_available_filenames']:
label = document_form.cleaned_data['document_type_available_filenames'].filename
task_upload_document.apply_async(kwargs=dict(
source_id=source.pk,
file_path=temporary_file.name,
label=label,
document_type_id=document_type.pk,
expand=expand,
metadata_dict_list=decode_metadata_from_url(request.GET),
user_id=user_id,
description=document_form.cleaned_data.get('description'),
language=document_form.cleaned_data.get('language')
), queue='uploads')
messages.success(request, _(u'New document queued for uploaded and will be available shortly.'))
return HttpResponseRedirect(request.get_full_path())
except Exception as exception:
if settings.DEBUG:
raise
messages.error(request, _(u'Unhandled exception: %s') % exception)
else: else:
upload_form = upload_form_class( self.source = InteractiveSource.objects.filter(enabled=True).select_subclasses().first()
show_expand=(source.uncompress == SOURCE_UNCOMPRESS_CHOICE_ASK),
source=source,
prefix='source',
)
document_form = NewDocumentForm(
document_type=document_type,
prefix='document')
title = _(u'Upload a local document from source: %s') % source.title return super(UploadInteractiveView, self).dispatch(request, *args, **kwargs)
if isinstance(source, StagingFolderSource): def forms_valid(self, forms):
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
uploaded_file = self.source.get_upload_file_object(self.request, forms['source_form'])
file_object = uploaded_file.file
temporary_file = tempfile.NamedTemporaryFile(delete=False)
for chunk in file_object.chunks():
temporary_file.write(chunk)
temporary_file.close()
file_object.close()
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
if 'document_type_available_filenames' in forms['document_form'].cleaned_data:
if forms['document_form'].cleaned_data['document_type_available_filenames']:
label = forms['document_form'].cleaned_data['document_type_available_filenames'].filename
task_upload_document.apply_async(kwargs=dict(
source_id=self.source.pk,
file_path=temporary_file.name,
label=file_object.name,
document_type_id=self.document_type.pk,
expand=expand,
metadata_dict_list=decode_metadata_from_url(self.request.GET),
user_id=user_id,
description=forms['document_form'].cleaned_data.get('description'),
language=forms['document_form'].cleaned_data.get('language')
), queue='uploads')
messages.success(self.request, _(u'New document queued for uploaded and will be available shortly.'))
return HttpResponseRedirect(self.request.get_full_path())
def create_source_form_form(self, **kwargs):
return self.get_form_classes()['source_form'](
prefix=kwargs['prefix'],
source=self.source,
show_expand=(self.source.uncompress == SOURCE_UNCOMPRESS_CHOICE_ASK),
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):
return {'document_form': NewDocumentForm, 'source_form': get_upload_form_class(self.source.source_type)}
def get_context_data(self, **kwargs):
context = super(UploadInteractiveView, self).get_context_data(**kwargs)
subtemplates_list = []
results = get_active_tab_links()
context['source'] = self.source
if InteractiveSource.objects.filter(enabled=True).count() == 0:
subtemplates_list.append(
{
'name': 'main/generic_subtemplate.html',
'context': {
'title': _(u'Upload sources'),
'paragraphs': [
_(u'No interactive document sources have been defined or none have been enabled.'),
],
}
})
title = _(u'Upload a local document from source: %s') % self.source.title
if isinstance(self.source, StagingFolderSource):
try: try:
staging_filelist = list(source.get_files()) staging_filelist = list(self.source.get_files())
except Exception as exception: except Exception as exception:
messages.error(request, exception) messages.error(request, exception)
staging_filelist = [] staging_filelist = []
@@ -239,7 +210,7 @@ def upload_interactive(request, source_id=None):
{ {
'name': 'main/generic_multiform_subtemplate.html', 'name': 'main/generic_multiform_subtemplate.html',
'context': { 'context': {
'forms': [upload_form, document_form], 'forms': context['forms'],
'title': title, 'title': title,
} }
}, },
@@ -256,52 +227,72 @@ def upload_interactive(request, source_id=None):
subtemplates_list.append({ subtemplates_list.append({
'name': 'main/generic_multiform_subtemplate.html', 'name': 'main/generic_multiform_subtemplate.html',
'context': { 'context': {
'forms': [upload_form, document_form], 'forms': context['forms'],
'title': title, 'title': title,
'is_multipart': True 'is_multipart': True
}, },
}) })
context.update({ context.update({
'document_type_id': document_type.pk, 'document_type_id': self.document_type.pk,
'subtemplates_list': subtemplates_list, 'subtemplates_list': subtemplates_list,
'temporary_navigation_links': { 'temporary_navigation_links': {
'form_header': { 'form_header': {
'sources:upload_version': { 'sources:upload_version': {
'links': results['tab_links'] 'links': results['tab_links']
}, },
'sources:upload_interactive': { 'sources:upload_interactive': {
'links': results['tab_links'] 'links': results['tab_links']
}
} }
},
})
context.update(
{
'sidebar_subtemplates_list': [
{
'name': 'main/generic_subtemplate.html',
'context': {
'title': _(u'Current document type'),
'paragraphs': [self.document_type if self.document_type else _(u'None')],
'side_bar': True,
}
},
{
'name': 'main/generic_subtemplate.html',
'context': {
'title': _(u'Current metadata'),
'paragraphs': metadata_repr_as_list(decode_metadata_from_url(self.request.GET)),
'side_bar': True,
}
}
],
} }
}, )
})
context.update( return context
{
'sidebar_subtemplates_list': [
{
'name': 'main/generic_subtemplate.html',
'context': {
'title': _(u'Current document type'),
'paragraphs': [document_type if document_type else _(u'None')],
'side_bar': True,
}
},
{
'name': 'main/generic_subtemplate.html',
'context': {
'title': _(u'Current metadata'),
'paragraphs': metadata_repr_as_list(decode_metadata_from_url(request.GET)),
'side_bar': True,
}
}
],
}
)
return render_to_response('main/generic_form.html', context,
context_instance=RequestContext(request)) def upload_new_version(request, document_id):
subtemplates_list = []
document = get_object_or_404(Document, pk=document_pk)
try:
Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_NEW_VERSION])
except PermissionDenied:
AccessEntry.objects.check_access(PERMISSION_DOCUMENT_NEW_VERSION, request.user, document)
results = get_active_tab_links(document)
# messages.success(request, _(u'New document version queued for uploaded and will be available shortly.'))
# return HttpResponseRedirect(reverse('documents:document_version_list', args=[document.pk]))
# title = _(u'Upload a new version from source: %s') % source.title
# TODO: move to version upload
#if document:
#context['object'] = document
pass
def staging_file_delete(request, staging_folder_pk, encoded_filename): def staging_file_delete(request, staging_folder_pk, encoded_filename):