diff --git a/apps/main/api.py b/apps/main/api.py deleted file mode 100644 index 8b13789179..0000000000 --- a/apps/main/api.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apps/sources/__init__.py b/apps/sources/__init__.py index 0cd2b962d0..9e5e1d3fa5 100644 --- a/apps/sources/__init__.py +++ b/apps/sources/__init__.py @@ -2,8 +2,8 @@ from __future__ import absolute_import from django.utils.translation import ugettext_lazy as _ -from navigation.api import (bind_links, - register_model_list_columns) +from navigation.api import (bind_links, register_model_list_columns, + register_sidebar_template) from common.utils import encapsulate from project_setup.api import register_setup from scheduler.api import LocalScheduler @@ -21,12 +21,15 @@ from .links import (staging_file_delete, setup_sources, setup_source_delete, setup_source_create, setup_source_log_list, setup_source_transformation_list, setup_source_transformation_create, setup_source_transformation_edit, setup_source_transformation_delete, - upload_version, document_create_multiple, setup_local_scanner_list) + upload_version, document_create_multiple, setup_local_scanner_list, + setup_local_scanners_refresh) bind_links([StagingFile], [staging_file_delete]) bind_links([SourceTransformation], [setup_source_transformation_edit, setup_source_transformation_delete]) +bind_links([LocalScanner, 'setup_local_scanner_list'], [setup_local_scanners_refresh], menu_name='secondary_menu') +register_sidebar_template([LocalScanner, 'setup_local_scanner_list'], 'scanner_list.html') bind_links(['setup_imap_email_list', 'setup_pop3_email_list', 'setup_web_form_list', 'setup_staging_folder_list', 'setup_watch_folder_list', 'setup_source_create', 'setup_local_scanner_list'], [setup_web_form_list, setup_staging_folder_list, setup_pop3_email_list, setup_imap_email_list, setup_local_scanner_list], menu_name='form_header') bind_links([WebForm, StagingFolder, POP3Email, IMAPEmail, LocalScanner, 'setup_web_form_list', 'setup_staging_folder_list', 'setup_watch_folder_list', 'setup_source_create', 'setup_pop3_email_list', 'setup_imap_email_list', 'setup_local_scanner_list'], [setup_source_create], menu_name='secondary_menu') diff --git a/apps/sources/forms.py b/apps/sources/forms.py index a2bd2d4f53..a61f0d49a5 100644 --- a/apps/sources/forms.py +++ b/apps/sources/forms.py @@ -105,14 +105,45 @@ class LocalScannerSetupForm(forms.ModelForm): model = LocalScanner scanner = forms.ChoiceField(required=False, label=_(u'Active scanners'), help_text=_(u'List of scanners found connected to this node. Choose one to have its device and description automatically saved in the fields above.')) - + scanner_device = forms.CharField(required=False, label=_(u'Scanner device')) + scanner_description = forms.CharField(required=False, label=_(u'Scanner description')) + def __init__(self, *args, **kwargs): super(LocalScannerSetupForm, self).__init__(*args, **kwargs) self.fields['icon'].widget = FamFamRadioSelect( attrs=self.fields['icon'].widget.attrs, choices=self.fields['icon'].widget.choices, ) - self.fields['scanner'].choices = LocalScanner.get_scanner_choices() + self.scanner_choices = LocalScanner.get_scanner_choices() + self.fields['scanner'].choices = self.scanner_choices + + def clean(self): + try: + scanner = LocalScanner.get_scanner(self.cleaned_data.get('scanner')) + except LocalScanner.NoSuchScanner: + device_name = u'' + description = u'' + else: + device_name = scanner['scanner']._device + description = scanner['description'] + + self.cleaned_data['scanner_device'] = device_name + self.cleaned_data['scanner_description'] = description + return self.cleaned_data + + +class LocalScannerForm(DocumentForm): + def __init__(self, *args, **kwargs): + show_expand = kwargs.pop('show_expand', False) + self.source = kwargs.pop('source') + super(LocalScannerForm, self).__init__(*args, **kwargs) + self.fields['new_filename'].help_text=_(u'If left blank a date time stamp will be used.') + + def clean_file(self): + data = self.cleaned_data['file'] + validate_whitelist_blacklist(data.name, self.source.whitelist.split(','), self.source.blacklist.split(',')) + + return data class SourceTransformationForm(forms.ModelForm): diff --git a/apps/sources/links.py b/apps/sources/links.py index 03b269201f..2d43fafa69 100644 --- a/apps/sources/links.py +++ b/apps/sources/links.py @@ -8,19 +8,18 @@ from documents.permissions import (PERMISSION_DOCUMENT_NEW_VERSION, from .permissions import (PERMISSION_SOURCES_SETUP_VIEW, PERMISSION_SOURCES_SETUP_EDIT, PERMISSION_SOURCES_SETUP_DELETE, PERMISSION_SOURCES_SETUP_CREATE) -from .models import (WebForm, StagingFolder, SourceTransformation, - WatchFolder, POP3Email, IMAPEmail, LocalScanner) staging_file_preview = Link(text=_(u'preview'), klass='fancybox-noscaling', view='staging_file_preview', args=['source.source_type', 'source.pk', 'object.pk'], sprite='zoom', permissions=[PERMISSION_DOCUMENT_NEW_VERSION, PERMISSION_DOCUMENT_CREATE]) staging_file_delete = Link(text=_(u'delete'), view='staging_file_delete', args=['source.source_type', 'source.pk', 'object.pk'], sprite='delete', keep_query=True, permissions=[PERMISSION_DOCUMENT_NEW_VERSION, PERMISSION_DOCUMENT_CREATE]) -setup_sources = Link(text=_(u'sources'), view='setup_web_form_list', sprite='application_form', icon='application_form.png', children_classes=[WebForm], permissions=[PERMISSION_SOURCES_SETUP_VIEW], children_view_regex=[r'setup_web_form', r'setup_staging_folder', r'setup_source_', r'setup_pop3', r'setup_imap']) -setup_web_form_list = Link(text=_(u'web forms'), view='setup_web_form_list', sprite='application_form', icon='application_form.png', children_classes=[WebForm], permissions=[PERMISSION_SOURCES_SETUP_VIEW]) -setup_staging_folder_list = Link(text=_(u'staging folders'), view='setup_staging_folder_list', sprite='folder_camera', children_classes=[StagingFolder], permissions=[PERMISSION_SOURCES_SETUP_VIEW]) -setup_watch_folder_list = Link(text=_(u'watch folders'), view='setup_watch_folder_list', sprite='folder_magnify', children_classes=[WatchFolder], permissions=[PERMISSION_SOURCES_SETUP_VIEW]) -setup_pop3_email_list = Link(text=_(u'POP3 email'), view='setup_pop3_email_list', sprite='email', children_classes=[POP3Email], permissions=[PERMISSION_SOURCES_SETUP_VIEW]) -setup_imap_email_list = Link(text=_(u'IMAP email'), view='setup_imap_email_list', sprite='email', children_classes=[IMAPEmail], permissions=[PERMISSION_SOURCES_SETUP_VIEW]) -setup_local_scanner_list = Link(text=_(u'Local scanner'), view='setup_local_scanner_list', sprite='images', children_classes=[LocalScanner], permissions=[PERMISSION_SOURCES_SETUP_VIEW]) +setup_sources = Link(text=_(u'sources'), view='setup_web_form_list', sprite='application_form', icon='application_form.png', permissions=[PERMISSION_SOURCES_SETUP_VIEW], children_view_regex=[r'setup_web_form', r'setup_staging_folder', r'setup_source_', r'setup_pop3', r'setup_imap']) +setup_web_form_list = Link(text=_(u'web forms'), view='setup_web_form_list', sprite='application_form', icon='application_form.png', permissions=[PERMISSION_SOURCES_SETUP_VIEW]) +setup_staging_folder_list = Link(text=_(u'staging folders'), view='setup_staging_folder_list', sprite='folder_camera', permissions=[PERMISSION_SOURCES_SETUP_VIEW]) +setup_watch_folder_list = Link(text=_(u'watch folders'), view='setup_watch_folder_list', sprite='folder_magnify', permissions=[PERMISSION_SOURCES_SETUP_VIEW]) +setup_pop3_email_list = Link(text=_(u'POP3 email'), view='setup_pop3_email_list', sprite='email', permissions=[PERMISSION_SOURCES_SETUP_VIEW]) +setup_imap_email_list = Link(text=_(u'IMAP email'), view='setup_imap_email_list', sprite='email', permissions=[PERMISSION_SOURCES_SETUP_VIEW]) +setup_local_scanner_list = Link(text=_(u'Local scanner'), view='setup_local_scanner_list', sprite='image', permissions=[PERMISSION_SOURCES_SETUP_VIEW]) +setup_local_scanners_refresh = Link(text=_(u'Refresh scanner list'), view='scanners_refresh', sprite='images', permissions=[PERMISSION_SOURCES_SETUP_VIEW]) setup_source_edit = Link(text=_(u'edit'), view='setup_source_edit', args=['source.source_type', 'source.pk'], sprite='application_form_edit', permissions=[PERMISSION_SOURCES_SETUP_EDIT]) setup_source_delete = Link(text=_(u'delete'), view='setup_source_delete', args=['source.source_type', 'source.pk'], sprite='application_form_delete', permissions=[PERMISSION_SOURCES_SETUP_DELETE]) diff --git a/apps/sources/literals.py b/apps/sources/literals.py index f2b6945f63..4e272e5596 100644 --- a/apps/sources/literals.py +++ b/apps/sources/literals.py @@ -64,7 +64,7 @@ SOURCE_CHOICES = ( (SOURCE_CHOICE_WATCH, _(u'server watch folder')), (SOURCE_CHOICE_POP3_EMAIL, _(u'POP3 email')), (SOURCE_CHOICE_IMAP_EMAIL, _(u'IMAP email')), - (SOURCE_CHOICE_LOCAL_SCANNER, _(u'Local scanner')), + (SOURCE_CHOICE_LOCAL_SCANNER, _(u'local scanner')), ) SOURCE_CHOICES_PLURAL = ( @@ -73,5 +73,7 @@ SOURCE_CHOICES_PLURAL = ( (SOURCE_CHOICE_WATCH, _(u'server watch folders')), (SOURCE_CHOICE_POP3_EMAIL, _(u'POP3 emails')), (SOURCE_CHOICE_IMAP_EMAIL, _(u'IMAP emails')), - (SOURCE_CHOICE_LOCAL_SCANNER, _(u'Local scanners')), + (SOURCE_CHOICE_LOCAL_SCANNER, _(u'local scanners')), ) + +DEFAULT_LOCAL_SCANNER_FILE_FORMAT = 'JPEG' diff --git a/apps/sources/models.py b/apps/sources/models.py index 3c02dd99f9..7345a76592 100644 --- a/apps/sources/models.py +++ b/apps/sources/models.py @@ -42,7 +42,8 @@ from .literals import (SOURCE_CHOICES, SOURCE_CHOICES_PLURAL, IMAP_PORT, IMAP_SSL_PORT, SOURCE_CHOICE_IMAP_EMAIL, DEFAULT_IMAP_INTERVAL, IMAP_DEFAULT_MAILBOX, - SOURCE_CHOICE_LOCAL_SCANNER, SOURCE_ICON_IMAGES) + SOURCE_CHOICE_LOCAL_SCANNER, SOURCE_ICON_IMAGES, + DEFAULT_LOCAL_SCANNER_FILE_FORMAT) from .compressed_file import CompressedFile, NotACompressedFile from .conf.settings import POP3_TIMEOUT #from . import sources_scheduler @@ -382,8 +383,12 @@ class IMAPEmail(EmailBaseModel): class LocalScanner(InteractiveBaseModel): + # scanner device string to scanner instance cache dict _scanner_cache = {} + class NoSuchScanner(Exception): + pass + is_interactive = True source_type = SOURCE_CHOICE_LOCAL_SCANNER default_icon = SOURCE_ICON_IMAGES @@ -393,33 +398,61 @@ class LocalScanner(InteractiveBaseModel): @classmethod def get_scanners(cls): - imagescanner.settings.ENABLE_NET_BACKEND = False - imagescanner.settings.ENABLE_TEST_BACKEND = False - iscanner = imagescanner.ImageScanner(remote_search=False) scanners = iscanner.list_scanners() + imagescanner.settings.LOGGING_LEVEL = logging.FATAL + imagescanner.settings.ENABLE_NET_BACKEND = False + imagescanner.settings.ENABLE_TEST_BACKEND = False for scanner in scanners: - LocalScanner._scanner_cache[scanner._device] = scanner + LocalScanner._scanner_cache[unicode(scanner._device)] = { + 'scanner': scanner, + 'description': u'%s: %s - %s - %s <%s>' % (scanner.id, scanner.manufacturer, scanner.name, scanner.description, scanner._device) + } return scanners + + @classmethod + def get_scanner(cls, device): + try: + return cls._scanner_cache[device] + except KeyError: + raise cls.NoSuchScanner @classmethod - def get_scanner_choices(cls): - scanners = cls.get_scanners() + def get_scanner_choices(cls, description_only=False): + if description_only: + template_func = lambda scanner: (scanner['description']) + else: + template_func = lambda scanner: (scanner['scanner']._device, scanner['description']) - return [(scanner._device, u'%s: %s - %s - %s <%s>' % (scanner.id, scanner.manufacturer, scanner.name, scanner.description, scanner._device)) for scanner in scanners] + return [template_func(scanner) for scanner in LocalScanner._scanner_cache.values()] - def get_scanner(self, fail=False): + def scanner(self, _fail=False): try: - return LocalScanner._scanner_cache[self.device] - except IndexError: - if fail==False: + return LocalScanner._scanner_cache[self.scanner_device]['scanner'] + except KeyError: + if _fail == False: + # Refresh the cache before trying one last time LocalScanner.get_scanners() - return self.get_scanner(fail=True) + return self.scanner(_fail=True) else: - # TODO: raise specialized exception - raise Exception('Error getting scanner') + raise self.__class__.NoSuchScanner + + def scan(self, as_image=False): + image = self.scanner().scan() + if as_image: + return image + else: + buf = StringIO() + image.save(buf, DEFAULT_LOCAL_SCANNER_FILE_FORMAT) + return PseudoFile(buf, name=unicode(datetime.datetime.now()).replace(u'.', u'_').replace(u' ', u'_')) + + # This code make new_version upload fail, use it for debugging + #buf = StringIO() + #buf.write(image.tostring()) + #buf.seek(0) + #return buf class Meta(InteractiveBaseModel.Meta): verbose_name = _(u'local scanner') diff --git a/apps/sources/templates/scanner_list.html b/apps/sources/templates/scanner_list.html new file mode 100644 index 0000000000..40ad540b26 --- /dev/null +++ b/apps/sources/templates/scanner_list.html @@ -0,0 +1,10 @@ +{% load i18n %} +{% load scanner_tags %} +{% trans 'Scanner list' as title %} + +{% scanner_list as paragraphs %} + +{% with 'true' as side_bar %} + {% include 'generic_subtemplate.html' %} +{% endwith %} + diff --git a/apps/sources/templatetags/__init__.py b/apps/sources/templatetags/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/sources/templatetags/scanner_tags.py b/apps/sources/templatetags/scanner_tags.py new file mode 100644 index 0000000000..966ec94f68 --- /dev/null +++ b/apps/sources/templatetags/scanner_tags.py @@ -0,0 +1,16 @@ +from django.template import Library +from django.utils.translation import ugettext_lazy as _ + +from sources.models import LocalScanner + +register = Library() + + +@register.assignment_tag +def scanner_list(): + list_of_scanners = LocalScanner.get_scanner_choices(description_only=True) + if list_of_scanners: + return list_of_scanners + else: + return [_(u'No scanners found.')] + diff --git a/apps/sources/urls.py b/apps/sources/urls.py index ae02bf0ea6..c2e20cc283 100644 --- a/apps/sources/urls.py +++ b/apps/sources/urls.py @@ -27,6 +27,7 @@ urlpatterns = patterns('sources.views', url(r'^setup/interactive/%s/list/$' % SOURCE_CHOICE_POP3_EMAIL, 'setup_source_list', {'source_type': SOURCE_CHOICE_POP3_EMAIL}, 'setup_pop3_email_list'), url(r'^setup/interactive/%s/list/$' % SOURCE_CHOICE_IMAP_EMAIL, 'setup_source_list', {'source_type': SOURCE_CHOICE_IMAP_EMAIL}, 'setup_imap_email_list'), url(r'^setup/interactive/%s/list/$' % SOURCE_CHOICE_LOCAL_SCANNER, 'setup_source_list', {'source_type': SOURCE_CHOICE_LOCAL_SCANNER}, 'setup_local_scanner_list'), + url(r'^setup/interactive/%s/refresh/$' % SOURCE_CHOICE_LOCAL_SCANNER, 'scanners_refresh', (), 'scanners_refresh'), url(r'^setup/interactive/(?P\w+)/list/$', 'setup_source_list', (), 'setup_source_list'), url(r'^setup/interactive/(?P\w+)/(?P\d+)/edit/$', 'setup_source_edit', (), 'setup_source_edit'), diff --git a/apps/sources/views.py b/apps/sources/views.py index e78cc3e40d..67c6bf44f3 100644 --- a/apps/sources/views.py +++ b/apps/sources/views.py @@ -38,7 +38,8 @@ from .literals import (SOURCE_UNCOMPRESS_CHOICE_Y, from .staging import create_staging_file_class from .forms import (StagingDocumentForm, WebFormForm, WatchFolderSetupForm, WebFormSetupForm, StagingFolderSetupForm, - POP3EmailSetupForm, IMAPEmailSetupForm, LocalScannerSetupForm) + POP3EmailSetupForm, IMAPEmailSetupForm, LocalScannerSetupForm, + LocalScannerForm) from .forms import SourceTransformationForm, SourceTransformationForm_create from .permissions import (PERMISSION_SOURCES_SETUP_VIEW, PERMISSION_SOURCES_SETUP_EDIT, PERMISSION_SOURCES_SETUP_DELETE, @@ -74,10 +75,15 @@ def get_active_tab_links(document=None): for staging_folder in staging_folders: tab_links.append(get_tab_link_for_source(staging_folder, document)) + local_scanners = LocalScanner.objects.filter(enabled=True) + for local_scanner in local_scanners: + tab_links.append(get_tab_link_for_source(local_scanner, document)) + return { 'tab_links': tab_links, SOURCE_CHOICE_WEB_FORM: web_forms, SOURCE_CHOICE_STAGING: staging_folders, + SOURCE_CHOICE_LOCAL_SCANNER: local_scanners, } @@ -99,7 +105,7 @@ def upload_interactive(request, source_type=None, source_id=None, document_pk=No context = {} - if results[SOURCE_CHOICE_WEB_FORM].count() == 0 and results[SOURCE_CHOICE_STAGING].count() == 0: + if results[SOURCE_CHOICE_WEB_FORM].count() == 0 and results[SOURCE_CHOICE_STAGING].count() == 0 and results[SOURCE_CHOICE_LOCAL_SCANNER].count() == 0: source_setup_link = mark_safe('%s' % (reverse('setup_web_form_list'), ugettext(u'here'))) subtemplates_list.append( { @@ -128,6 +134,9 @@ def upload_interactive(request, source_type=None, source_id=None, document_pk=No elif results[SOURCE_CHOICE_STAGING].count(): source_type = results[SOURCE_CHOICE_STAGING][0].source_type source_id = results[SOURCE_CHOICE_STAGING][0].pk + elif results[SOURCE_CHOICE_LOCAL_SCANNER].count(): + source_type = results[SOURCE_CHOICE_LOCAL_SCANNER][0].source_type + source_id = results[SOURCE_CHOICE_LOCAL_SCANNER][0].pk if source_type and source_id: if source_type == SOURCE_CHOICE_WEB_FORM: @@ -300,6 +309,65 @@ def upload_interactive(request, source_type=None, source_id=None, document_pk=No } }, ] + elif source_type == SOURCE_CHOICE_LOCAL_SCANNER: + local_scanner = get_object_or_404(LocalScanner, pk=source_id) + context['source'] = local_scanner + if request.method == 'POST': + form = LocalScannerForm(request.POST, request.FILES, + document_type=document_type, + source=local_scanner, + instance=document + ) + if form.is_valid(): + try: + expand = False + + new_filename = get_form_filename(form) + + image = local_scanner.scan() + + result = local_scanner.upload_file(image, + new_filename, use_file_name=form.cleaned_data.get('use_file_name', False), + document_type=document_type, + expand=expand, + metadata_dict_list=decode_metadata_from_url(request.GET), + user=request.user, + document=document, + new_version_data=form.cleaned_data.get('new_version_data') + ) + if document: + messages.success(request, _(u'New document version uploaded successfully.')) + return HttpResponseRedirect(reverse('document_view_simple', args=[document.pk])) + else: + messages.success(request, _(u'File uploaded successfully.')) + + return HttpResponseRedirect(request.get_full_path()) + except NewDocumentVersionNotAllowed: + messages.error(request, _(u'New version uploads are not allowed for this document.')) + except Exception, e: + if settings.DEBUG: + raise + messages.error(request, _(u'Unhandled exception: %s') % e) + else: + form = LocalScannerForm( + document_type=document_type, + source=local_scanner, + instance=document + ) + if document: + title = _(u'upload a new version scanned from source: %s') % local_scanner.title + else: + title = _(u'upload a document scanned from source: %s') % local_scanner.title + + subtemplates_list.append({ + 'name': 'generic_form_subtemplate.html', + 'context': { + 'form': form, + 'title': title, + 'submit_label': _(u'Scan'), + 'submit_icon_famfam': 'image', + }, + }) if document: context['object'] = document @@ -819,3 +887,15 @@ def document_create(request): wizard = DocumentCreateWizard(form_list=[DocumentTypeSelectForm, MetadataSelectionForm, MetadataFormSet]) return wizard(request) + + +def scanners_refresh(request): + previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', '/'))) + try: + LocalScanner.get_scanners() + messages.success(request, _(u'Scanner list refreshed successfully.')) + except: + messages.error(request, _(u'Scanner list refreshed error.')) + + return HttpResponseRedirect(previous) +