From 388a4b0dd7499c2aa81adeac5e47a88a6ecf9d69 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Wed, 8 Aug 2012 05:21:42 -0400 Subject: [PATCH 1/4] Initial import to support local scanner sources --- apps/sources/__init__.py | 21 +-- apps/sources/forms.py | 17 +- apps/sources/links.py | 3 +- apps/sources/literals.py | 5 + .../migrations/0013_auto__add_localscanner.py | 150 ++++++++++++++++++ apps/sources/models.py | 27 +++- .../static/images/icons/image_delete.png | Bin 0 -> 1707 bytes apps/sources/urls.py | 4 +- apps/sources/views.py | 31 +++- docs/releases/0.13.rst | 1 + requirements/production.txt | 4 + 11 files changed, 244 insertions(+), 19 deletions(-) create mode 100644 apps/sources/migrations/0013_auto__add_localscanner.py create mode 100755 apps/sources/static/images/icons/image_delete.png diff --git a/apps/sources/__init__.py b/apps/sources/__init__.py index 64db8aeaab..0cd2b962d0 100644 --- a/apps/sources/__init__.py +++ b/apps/sources/__init__.py @@ -11,7 +11,7 @@ from documents.models import Document from .staging import StagingFile from .models import (WebForm, StagingFolder, SourceTransformation, - WatchFolder, POP3Email, IMAPEmail) + WatchFolder, POP3Email, IMAPEmail, LocalScanner) from .widgets import staging_file_thumbnail from .tasks import task_fetch_pop3_emails, task_fetch_imap_emails from .conf.settings import EMAIL_PROCESSING_INTERVAL @@ -21,32 +21,35 @@ 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) + upload_version, document_create_multiple, setup_local_scanner_list) bind_links([StagingFile], [staging_file_delete]) bind_links([SourceTransformation], [setup_source_transformation_edit, setup_source_transformation_delete]) -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_web_form_list, setup_staging_folder_list, setup_pop3_email_list, setup_imap_email_list], menu_name='form_header') -bind_links([WebForm, StagingFolder, POP3Email, IMAPEmail, 'setup_web_form_list', 'setup_staging_folder_list', 'setup_watch_folder_list', 'setup_source_create', 'setup_pop3_email_list', 'setup_imap_email_list'], [setup_source_create], menu_name='secondary_menu') +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') -bind_links([WebForm], [setup_web_form_list, setup_staging_folder_list, setup_pop3_email_list, setup_imap_email_list], menu_name='form_header') +bind_links([WebForm], [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], [setup_source_transformation_list, setup_source_edit, setup_source_delete]) -bind_links([StagingFolder], [setup_web_form_list, setup_staging_folder_list, setup_pop3_email_list, setup_imap_email_list], menu_name='form_header') +bind_links([StagingFolder], [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([StagingFolder], [setup_source_transformation_list, setup_source_edit, setup_source_delete]) -bind_links([POP3Email], [setup_web_form_list, setup_staging_folder_list, setup_pop3_email_list, setup_imap_email_list], menu_name='form_header') +bind_links([POP3Email], [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([POP3Email], [setup_source_transformation_list, setup_source_edit, setup_source_delete]) bind_links([POP3Email], [setup_source_log_list]) -bind_links([IMAPEmail], [setup_web_form_list, setup_staging_folder_list, setup_pop3_email_list, setup_imap_email_list], menu_name='form_header') +bind_links([IMAPEmail], [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([IMAPEmail], [setup_source_transformation_list, setup_source_edit, setup_source_delete]) bind_links([IMAPEmail], [setup_source_log_list]) -bind_links([WatchFolder], [setup_web_form_list, setup_staging_folder_list, setup_watch_folder_list, setup_imap_email_list], menu_name='form_header') +bind_links([WatchFolder], [setup_web_form_list, setup_staging_folder_list, setup_watch_folder_list, setup_imap_email_list, setup_local_scanner_list], menu_name='form_header') bind_links([WatchFolder], [setup_source_transformation_list, setup_source_edit, setup_source_delete]) +bind_links([LocalScanner], [setup_web_form_list, setup_staging_folder_list, setup_watch_folder_list, setup_imap_email_list, setup_local_scanner_list], menu_name='form_header') +bind_links([LocalScanner], [setup_source_transformation_list, setup_source_edit, setup_source_delete]) + # Document version bind_links(['document_version_list', 'upload_version', 'document_version_revert', 'document_version_text_compare', 'document_version_show_diff_text'], [upload_version], menu_name='sidebar') diff --git a/apps/sources/forms.py b/apps/sources/forms.py index 84b6f79281..a2bd2d4f53 100644 --- a/apps/sources/forms.py +++ b/apps/sources/forms.py @@ -7,7 +7,7 @@ from django.utils.translation import ugettext from documents.forms import DocumentForm from .models import (WebForm, StagingFolder, SourceTransformation, - WatchFolder, POP3Email, IMAPEmail) + WatchFolder, POP3Email, IMAPEmail, LocalScanner) from .widgets import FamFamRadioSelect from .utils import validate_whitelist_blacklist @@ -100,6 +100,21 @@ class WatchFolderSetupForm(forms.ModelForm): model = WatchFolder +class LocalScannerSetupForm(forms.ModelForm): + class Meta: + 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.')) + + 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() + + class SourceTransformationForm(forms.ModelForm): class Meta: model = SourceTransformation diff --git a/apps/sources/links.py b/apps/sources/links.py index 1bebe3ac6f..03b269201f 100644 --- a/apps/sources/links.py +++ b/apps/sources/links.py @@ -9,7 +9,7 @@ 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) + 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]) @@ -20,6 +20,7 @@ setup_staging_folder_list = Link(text=_(u'staging folders'), view='setup_staging 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_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 8faeb2f4aa..f2b6945f63 100644 --- a/apps/sources/literals.py +++ b/apps/sources/literals.py @@ -35,6 +35,7 @@ SOURCE_ICON_FOLDER = 'folder' SOURCE_ICON_WORLD = 'world' SOURCE_ICON_PRINTER = 'printer' SOURCE_ICON_PRINTER_EMPTY = 'printer_empty' +SOURCE_ICON_IMAGES = 'images' SOURCE_ICON_CHOICES = ( (SOURCE_ICON_DISK, _(u'Disk')), @@ -44,6 +45,7 @@ SOURCE_ICON_CHOICES = ( (SOURCE_ICON_DRIVE_USER, _(u'User drive')), (SOURCE_ICON_EMAIL, _(u'Envelope')), (SOURCE_ICON_FOLDER, _(u'Folder')), + (SOURCE_ICON_IMAGES, _(u'Images')), (SOURCE_ICON_WORLD, _(u'World')), (SOURCE_ICON_PRINTER, _(u'Printer')), (SOURCE_ICON_PRINTER_EMPTY, _(u'Empty printer')), @@ -54,6 +56,7 @@ SOURCE_CHOICE_STAGING = 'staging' SOURCE_CHOICE_WATCH = 'watch' SOURCE_CHOICE_POP3_EMAIL = 'pop3' SOURCE_CHOICE_IMAP_EMAIL = 'imap' +SOURCE_CHOICE_LOCAL_SCANNER = 'local_scanner' SOURCE_CHOICES = ( (SOURCE_CHOICE_WEB_FORM, _(u'web form')), @@ -61,6 +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_CHOICES_PLURAL = ( @@ -69,4 +73,5 @@ 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')), ) diff --git a/apps/sources/migrations/0013_auto__add_localscanner.py b/apps/sources/migrations/0013_auto__add_localscanner.py new file mode 100644 index 0000000000..c4f088cadf --- /dev/null +++ b/apps/sources/migrations/0013_auto__add_localscanner.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'LocalScanner' + db.create_table('sources_localscanner', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=64)), + ('enabled', self.gf('django.db.models.fields.BooleanField')(default=True)), + ('whitelist', self.gf('django.db.models.fields.TextField')(blank=True)), + ('blacklist', self.gf('django.db.models.fields.TextField')(blank=True)), + ('icon', self.gf('django.db.models.fields.CharField')(max_length=24, null=True, blank=True)), + ('scanner_device', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('scanner_description', self.gf('django.db.models.fields.CharField')(max_length=255)), + )) + db.send_create_signal('sources', ['LocalScanner']) + + + def backwards(self, orm): + # Deleting model 'LocalScanner' + db.delete_table('sources_localscanner') + + + models = { + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'documents.documenttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'DocumentType'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32'}) + }, + 'sources.imapemail': { + 'Meta': {'ordering': "('title',)", 'object_name': 'IMAPEmail'}, + 'blacklist': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'document_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.DocumentType']", 'null': 'True', 'blank': 'True'}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'host': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'interval': ('django.db.models.fields.PositiveIntegerField', [], {'default': '900'}), + 'mailbox': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'port': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'ssl': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'uncompress': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + 'username': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'whitelist': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + }, + 'sources.localscanner': { + 'Meta': {'ordering': "('title',)", 'object_name': 'LocalScanner'}, + 'blacklist': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'icon': ('django.db.models.fields.CharField', [], {'max_length': '24', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'scanner_description': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'scanner_device': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'whitelist': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + }, + 'sources.outofprocess': { + 'Meta': {'ordering': "('title',)", 'object_name': 'OutOfProcess'}, + 'blacklist': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'whitelist': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + }, + 'sources.pop3email': { + 'Meta': {'ordering': "('title',)", 'object_name': 'POP3Email'}, + 'blacklist': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'document_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.DocumentType']", 'null': 'True', 'blank': 'True'}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'host': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'interval': ('django.db.models.fields.PositiveIntegerField', [], {'default': '900'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'port': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'ssl': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'uncompress': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + 'username': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'whitelist': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + }, + 'sources.sourcelog': { + 'Meta': {'ordering': "('creation_datetime',)", 'object_name': 'SourceLog'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'creation_datetime': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'status': ('django.db.models.fields.TextField', [], {}) + }, + 'sources.sourcetransformation': { + 'Meta': {'ordering': "('order',)", 'object_name': 'SourceTransformation'}, + 'arguments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'transformation': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'sources.stagingfolder': { + 'Meta': {'ordering': "('title',)", 'object_name': 'StagingFolder'}, + 'blacklist': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'delete_after_upload': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'folder_path': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'icon': ('django.db.models.fields.CharField', [], {'max_length': '24', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'preview_height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'preview_width': ('django.db.models.fields.IntegerField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'uncompress': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + 'whitelist': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + }, + 'sources.watchfolder': { + 'Meta': {'ordering': "('title',)", 'object_name': 'WatchFolder'}, + 'blacklist': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'delete_after_upload': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'folder_path': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'interval': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'uncompress': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + 'whitelist': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + }, + 'sources.webform': { + 'Meta': {'ordering': "('title',)", 'object_name': 'WebForm'}, + 'blacklist': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'icon': ('django.db.models.fields.CharField', [], {'max_length': '24', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'uncompress': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + 'whitelist': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + } + } + + complete_apps = ['sources'] \ No newline at end of file diff --git a/apps/sources/models.py b/apps/sources/models.py index 9c779eebe7..afaf5a004d 100644 --- a/apps/sources/models.py +++ b/apps/sources/models.py @@ -14,6 +14,8 @@ try: except ImportError: from StringIO import StringIO +import imagescanner + from django.db import models from django.utils.translation import ugettext_lazy as _ from django.contrib.contenttypes.models import ContentType @@ -39,7 +41,8 @@ from .literals import (SOURCE_CHOICES, SOURCE_CHOICES_PLURAL, SOURCE_CHOICE_POP3_EMAIL, DEFAULT_POP3_INTERVAL, IMAP_PORT, IMAP_SSL_PORT, SOURCE_CHOICE_IMAP_EMAIL, DEFAULT_IMAP_INTERVAL, - IMAP_DEFAULT_MAILBOX) + IMAP_DEFAULT_MAILBOX, + SOURCE_CHOICE_LOCAL_SCANNER, SOURCE_ICON_IMAGES) from .compressed_file import CompressedFile, NotACompressedFile from .conf.settings import POP3_TIMEOUT #from . import sources_scheduler @@ -378,6 +381,28 @@ class IMAPEmail(EmailBaseModel): verbose_name_plural = _(u'IMAP email') +class LocalScanner(InteractiveBaseModel): + is_interactive = True + source_type = SOURCE_CHOICE_LOCAL_SCANNER + default_icon = SOURCE_ICON_IMAGES + + scanner_device = models.CharField(max_length=255, verbose_name=_(u'scanner device')) + scanner_description = models.CharField(max_length=255, verbose_name=_(u'scanner description')) + + @classmethod + def get_scanner_choices(cls): + imagescanner.settings.ENABLE_NET_BACKEND = False + imagescanner.settings.ENABLE_TEST_BACKEND = False + + iscanner = imagescanner.ImageScanner(remote_search=False) + scanners = iscanner.list_scanners() + return [(scanner._device, u'%s: %s - %s - %s <%s>' % (scanner.id, scanner.manufacturer, scanner.name, scanner.description, scanner._device)) for scanner in scanners] + + class Meta(InteractiveBaseModel.Meta): + verbose_name = _(u'local scanner') + verbose_name_plural = _(u'local scanners') + + class StagingFolder(InteractiveBaseModel): is_interactive = True source_type = SOURCE_CHOICE_STAGING diff --git a/apps/sources/static/images/icons/image_delete.png b/apps/sources/static/images/icons/image_delete.png new file mode 100755 index 0000000000000000000000000000000000000000..580f81a22e1143536980f48569880b8cb83e4d7e GIT binary patch literal 1707 zcmV;c22}ZpP)LBpYPlLFqdKZzDLQF2>#h?51<$UTJesW^0odC$l9t)xxf=|9q$l4%5XJ-v{+up z$@c?%UnV|BodC}ym}Q(woJJ|yyX_WEt%uW72`HU>tO&lTB{9FA>-scQV;W9FhR*`1 zG{4Po3{o^H1?7^d&(Y)Phy?UCN9EZ$T)3qe-|RnV`B;i;z|zK7?)!$?zSY{2~U_1@SbbqtprfYP{5^$2$66? zsprna1MmgZp9~&&m5`XVFZX<#gr@XTw)B|6N(Bi44egYmisOdBoJpvva=-u>kObV2 zv0gyRH@yJTj6C**>jgyDdZ2Sz=zJd0<$lf!(AOD*;Ud)40-TrjL)GK~Nk*|uyx=FG zWg?~;XkkXmgw|!d5D)bcr~s7sU6J?3G{2)6^=zpsf{vNcx#8OSfGYQ8Z^8pIH`e=s zwOW-B_n(F?L=>8spJ93;!x~y$1DzHSP)If0N8~!*^!pQZ- z*!c1b2+#h4!lxe~ywYc_T-=U^yY9i7SN1`F`2I7UexSGg@cUjN;Y@RfY%HR;@4O9; z#9ER^#KcIM=3+u2WH`_FN61}iz@2;cV(g2LG1hq)7-9C;HyM%nSm&2~_6gSR-iyYT zmfat1ShYp8O(J0Q6EN;2I6VM4!7#ZHGqWVS%Ocs%=PH)$5ApY@G2FVn1EsHzA|B}F zZpyf`oE?VIh|GB}>hHz)iKAG4e+SwF_W*3BRc*(V;>GiFNF#7c;z1C9VwW=q4SGm+ zyP|FIDs*tQtBBP*nlbuq7sEkv-F?#t!K^3 z4vKe{!s$6@3}`-fde-lT9@+%e(_N+)O!GjcCTlnmOF9?@43V^A$4}2m+`N;!R*t?o zx}e;kWZ|f3o||2vIn~C9uVvvjHDlz-`=G;c!mU+orL^UpIG3w{8UmiaKQTHD%~I&94XK0O7@`M_ZdAIuFQA4aE4)x+$-dU(g1#~bC`uIKX;LWP?9#4>Ec zbCi(PS9E3bN1i&_^&%f#Qppjf(f*3uPy~?ZSa4|l z%GXydYTkbPisfi+ZM7SlT+E5V!9kq6&_nRyV^5zt`>HrX{*cs^^T>@5C~u@+jOFV2wmC|#~p-Ch0>U;rWF4HaEIto#4~002ovPDHLkV1li( BN~!<= literal 0 HcmV?d00001 diff --git a/apps/sources/urls.py b/apps/sources/urls.py index 333b687b91..ae02bf0ea6 100644 --- a/apps/sources/urls.py +++ b/apps/sources/urls.py @@ -3,7 +3,8 @@ from __future__ import absolute_import from django.conf.urls.defaults import patterns, url from .literals import (SOURCE_CHOICE_WEB_FORM, SOURCE_CHOICE_STAGING, - SOURCE_CHOICE_WATCH, SOURCE_CHOICE_POP3_EMAIL, SOURCE_CHOICE_IMAP_EMAIL) + SOURCE_CHOICE_WATCH, SOURCE_CHOICE_POP3_EMAIL, SOURCE_CHOICE_IMAP_EMAIL, + SOURCE_CHOICE_LOCAL_SCANNER) urlpatterns = patterns('sources.views', url(r'^create/from/local/multiple/$', 'document_create', (), 'document_create_multiple'), @@ -25,6 +26,7 @@ urlpatterns = patterns('sources.views', url(r'^setup/interactive/%s/list/$' % SOURCE_CHOICE_WATCH, 'setup_source_list', {'source_type': SOURCE_CHOICE_WATCH}, 'setup_watch_folder_list'), 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/(?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 3e8aa2511a..e78cc3e40d 100644 --- a/apps/sources/views.py +++ b/apps/sources/views.py @@ -29,16 +29,16 @@ from acls.models import AccessEntry from navigation.api import Link from .models import (WebForm, StagingFolder, SourceTransformation, - WatchFolder, POP3Email, SourceLog, IMAPEmail) + WatchFolder, POP3Email, SourceLog, IMAPEmail, LocalScanner) from .literals import (SOURCE_CHOICE_WEB_FORM, SOURCE_CHOICE_STAGING, - SOURCE_CHOICE_WATCH, SOURCE_CHOICE_POP3_EMAIL, SOURCE_CHOICE_IMAP_EMAIL) + SOURCE_CHOICE_WATCH, SOURCE_CHOICE_POP3_EMAIL, SOURCE_CHOICE_IMAP_EMAIL, + SOURCE_CHOICE_LOCAL_SCANNER) from .literals import (SOURCE_UNCOMPRESS_CHOICE_Y, SOURCE_UNCOMPRESS_CHOICE_ASK) from .staging import create_staging_file_class from .forms import (StagingDocumentForm, WebFormForm, - WatchFolderSetupForm) -from .forms import (WebFormSetupForm, StagingFolderSetupForm, - POP3EmailSetupForm, IMAPEmailSetupForm) + WatchFolderSetupForm, WebFormSetupForm, StagingFolderSetupForm, + POP3EmailSetupForm, IMAPEmailSetupForm, LocalScannerSetupForm) from .forms import SourceTransformationForm, SourceTransformationForm_create from .permissions import (PERMISSION_SOURCES_SETUP_VIEW, PERMISSION_SOURCES_SETUP_EDIT, PERMISSION_SOURCES_SETUP_DELETE, @@ -446,6 +446,8 @@ def setup_source_list(request, source_type): cls = POP3Email elif source_type == SOURCE_CHOICE_IMAP_EMAIL: cls = IMAPEmail + elif source_type == SOURCE_CHOICE_LOCAL_SCANNER: + cls = LocalScanner context = { 'object_list': cls.objects.all(), @@ -480,6 +482,9 @@ def setup_source_edit(request, source_type, source_id): elif source_type == SOURCE_CHOICE_IMAP_EMAIL: cls = IMAPEmail form_class = IMAPEmailSetupForm + elif source_type == SOURCE_CHOICE_LOCAL_SCANNER: + cls = LocalScanner + form_class = LocalScannerSetupForm source = get_object_or_404(cls, pk=source_id) next = request.POST.get('next', request.GET.get('next', request.META.get('HTTP_REFERER', '/'))) @@ -530,6 +535,10 @@ def setup_source_delete(request, source_type, source_id): cls = IMAPEmail form_icon = u'email_delete.png' redirect_view = 'setup_imap_email_list' + elif source_type == SOURCE_CHOICE_LOCAL_SCANNER: + cls = LocalScanner + form_icon = u'image_delete.png' + redirect_view = 'setup_local_scanner_list' redirect_view = reverse('setup_source_list', args=[source_type]) previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', redirect_view))) @@ -588,7 +597,11 @@ def setup_source_create(request, source_type): cls = IMAPEmail form_class = IMAPEmailSetupForm redirect_view = 'setup_imap_email_list' - + elif source_type == SOURCE_CHOICE_LOCAL_SCANNER: + cls = LocalScanner + form_class = LocalScannerSetupForm + redirect_view = 'setup_local_scanner_list' + if request.method == 'POST': form = form_class(data=request.POST) if form.is_valid(): @@ -623,6 +636,8 @@ def setup_source_log_list(request, source_type, source_pk): cls = POP3Email elif source_type == SOURCE_CHOICE_IMAP_EMAIL: cls = IMAPEmail + elif source_type == SOURCE_CHOICE_LOCAL_SCANNER: + cls = LocalScanner source = get_object_or_404(cls, pk=source_pk) @@ -659,6 +674,8 @@ def setup_source_transformation_list(request, source_type, source_id): cls = POP3Email elif source_type == SOURCE_CHOICE_IMAP_EMAIL: cls = IMAPEmail + elif source_type == SOURCE_CHOICE_LOCAL_SCANNER: + cls = LocalScanner source = get_object_or_404(cls, pk=source_id) @@ -765,6 +782,8 @@ def setup_source_transformation_create(request, source_type, source_id): cls = POP3Email elif source_type == SOURCE_CHOICE_IMAP_EMAIL: cls = IMAPEmail + elif source_type == SOURCE_CHOICE_LOCAL_SCANNER: + cls = LocalScanner source = get_object_or_404(cls, pk=source_id) diff --git a/docs/releases/0.13.rst b/docs/releases/0.13.rst index 3a36a675b5..40b6e27991 100644 --- a/docs/releases/0.13.rst +++ b/docs/releases/0.13.rst @@ -88,6 +88,7 @@ Afterwards migrate existing database schema with:: $ ./manage.py migrate job_processor $ ./manage.py migrate clustering $ ./manage.py migrate trash + $ ./manage.py migrate sources Issue the following command to index existing documents in the new full text search database:: diff --git a/requirements/production.txt b/requirements/production.txt index 9055a27f2f..7844e4d0c0 100644 --- a/requirements/production.txt +++ b/requirements/production.txt @@ -68,6 +68,10 @@ pbs==0.105 psutil==0.5.1 GitPython==0.3.2.RC1 +# Sources + +-e git+https://github.com/rosarior/imagescanner.git#egg=imagescanner + # Misc elementtree==1.2.7-20070827-preview From be55d23c34028547e499b8796849462e618aace1 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Wed, 8 Aug 2012 05:39:46 -0400 Subject: [PATCH 2/4] Cache found scanners to avoid further searches --- apps/sources/models.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/apps/sources/models.py b/apps/sources/models.py index afaf5a004d..3c02dd99f9 100644 --- a/apps/sources/models.py +++ b/apps/sources/models.py @@ -382,6 +382,8 @@ class IMAPEmail(EmailBaseModel): class LocalScanner(InteractiveBaseModel): + _scanner_cache = {} + is_interactive = True source_type = SOURCE_CHOICE_LOCAL_SCANNER default_icon = SOURCE_ICON_IMAGES @@ -390,14 +392,35 @@ class LocalScanner(InteractiveBaseModel): scanner_description = models.CharField(max_length=255, verbose_name=_(u'scanner description')) @classmethod - def get_scanner_choices(cls): + 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() + + for scanner in scanners: + LocalScanner._scanner_cache[scanner._device] = scanner + + return scanners + + @classmethod + def get_scanner_choices(cls): + scanners = cls.get_scanners() + return [(scanner._device, u'%s: %s - %s - %s <%s>' % (scanner.id, scanner.manufacturer, scanner.name, scanner.description, scanner._device)) for scanner in scanners] + def get_scanner(self, fail=False): + try: + return LocalScanner._scanner_cache[self.device] + except IndexError: + if fail==False: + LocalScanner.get_scanners() + return self.get_scanner(fail=True) + else: + # TODO: raise specialized exception + raise Exception('Error getting scanner') + class Meta(InteractiveBaseModel.Meta): verbose_name = _(u'local scanner') verbose_name_plural = _(u'local scanners') From 2860ad46af917e6f7e14a3e801c9985e4820dcc1 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Thu, 9 Aug 2012 04:55:14 -0400 Subject: [PATCH 3/4] Updates for a working local scanner source --- apps/main/api.py | 1 - apps/sources/__init__.py | 9 ++- apps/sources/forms.py | 35 +++++++++- apps/sources/links.py | 17 +++-- apps/sources/literals.py | 6 +- apps/sources/models.py | 63 +++++++++++++---- apps/sources/templates/scanner_list.html | 10 +++ apps/sources/templatetags/__init__.py | 0 apps/sources/templatetags/scanner_tags.py | 16 +++++ apps/sources/urls.py | 1 + apps/sources/views.py | 84 ++++++++++++++++++++++- 11 files changed, 208 insertions(+), 34 deletions(-) delete mode 100644 apps/main/api.py create mode 100644 apps/sources/templates/scanner_list.html create mode 100644 apps/sources/templatetags/__init__.py create mode 100644 apps/sources/templatetags/scanner_tags.py 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) + From d341800ec5a719305844841cca6251064cf61f5f Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Thu, 9 Aug 2012 04:55:44 -0400 Subject: [PATCH 4/4] Add more debugging --- apps/documents/models.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/documents/models.py b/apps/documents/models.py index 7f3d253c8d..34ca755520 100644 --- a/apps/documents/models.py +++ b/apps/documents/models.py @@ -190,14 +190,18 @@ class Document(models.Model): serial=serial, comment=comment, ) + logger.debug('pre new_version.save() in version_update') new_version.save() + logger.debug('post new_version.save() in version_update') else: new_version_dict = {} new_version = DocumentVersion( document=self, file=file, ) + logger.debug('pre new_version.save() not in version_update') new_version.save() + logger.debug('post new_version.save() not in version_update') logger.debug('new_version saved') return new_version @@ -399,7 +403,9 @@ class DocumentVersion(models.Model): #Only do this for new documents transformations = kwargs.pop('transformations', None) + logger.debug('pre save in DocumentVersion.save()') super(DocumentVersion, self).save(*args, **kwargs) + logger.debug('post save in DocumentVersion.save()') for key in sorted(DocumentVersion._post_save_hooks): DocumentVersion._post_save_hooks[key](self)