Merge branch 'feature/sane_source'
Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
@@ -2,6 +2,7 @@ XX (2017-XX-XX)
|
||||
===============
|
||||
- Add Django-mathfilters.
|
||||
- Improve render of documents with no pages.
|
||||
- Add SANE scanner document source.
|
||||
|
||||
2.3 (2017-06-08)
|
||||
================
|
||||
|
||||
@@ -24,10 +24,11 @@ from .handlers import (
|
||||
from .links import (
|
||||
link_document_create_multiple, link_setup_sources,
|
||||
link_setup_source_check_now, link_setup_source_create_imap_email,
|
||||
link_setup_source_create_pop3_email, link_setup_source_create_watch_folder,
|
||||
link_setup_source_create_webform, link_setup_source_create_staging_folder,
|
||||
link_setup_source_delete, link_setup_source_edit, link_setup_source_logs,
|
||||
link_staging_file_delete, link_upload_version
|
||||
link_setup_source_create_pop3_email, link_setup_source_create_sane_scanner,
|
||||
link_setup_source_create_watch_folder, link_setup_source_create_webform,
|
||||
link_setup_source_create_staging_folder, link_setup_source_delete,
|
||||
link_setup_source_edit, link_setup_source_logs, link_staging_file_delete,
|
||||
link_upload_version
|
||||
)
|
||||
from .widgets import StagingFileThumbnailWidget
|
||||
|
||||
@@ -44,6 +45,7 @@ class SourcesApp(MayanAppConfig):
|
||||
IMAPEmail = self.get_model('IMAPEmail')
|
||||
Source = self.get_model('Source')
|
||||
SourceLog = self.get_model('SourceLog')
|
||||
SaneScanner = self.get_model('SaneScanner')
|
||||
StagingFolderSource = self.get_model('StagingFolderSource')
|
||||
WatchFolderSource = self.get_model('WatchFolderSource')
|
||||
WebFormSource = self.get_model('WebFormSource')
|
||||
@@ -119,8 +121,8 @@ class SourcesApp(MayanAppConfig):
|
||||
link_setup_source_edit, link_setup_source_delete,
|
||||
link_transformation_list, link_setup_source_logs
|
||||
), sources=(
|
||||
POP3Email, IMAPEmail, StagingFolderSource, WatchFolderSource,
|
||||
WebFormSource
|
||||
POP3Email, IMAPEmail, SaneScanner, StagingFolderSource,
|
||||
WatchFolderSource, WebFormSource
|
||||
)
|
||||
)
|
||||
menu_object.bind_links(
|
||||
@@ -133,6 +135,7 @@ class SourcesApp(MayanAppConfig):
|
||||
menu_secondary.bind_links(
|
||||
links=(
|
||||
link_setup_sources, link_setup_source_create_webform,
|
||||
link_setup_source_create_sane_scanner,
|
||||
link_setup_source_create_staging_folder,
|
||||
link_setup_source_create_pop3_email,
|
||||
link_setup_source_create_imap_email,
|
||||
|
||||
8
mayan/apps/sources/exceptions.py
Normal file
8
mayan/apps/sources/exceptions.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
class SourceException(Exception):
|
||||
"""
|
||||
Base sources warning
|
||||
"""
|
||||
pass
|
||||
@@ -9,7 +9,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from documents.forms import DocumentForm
|
||||
|
||||
from .models import (
|
||||
IMAPEmail, POP3Email, StagingFolderSource, WebFormSource,
|
||||
IMAPEmail, POP3Email, SaneScanner, StagingFolderSource, WebFormSource,
|
||||
WatchFolderSource
|
||||
)
|
||||
|
||||
@@ -79,6 +79,19 @@ class WebFormUploadFormHTML5(WebFormUploadForm):
|
||||
)
|
||||
|
||||
|
||||
class SaneScannerUploadForm(UploadBaseForm):
|
||||
pass
|
||||
|
||||
|
||||
class SaneScannerSetupForm(forms.ModelForm):
|
||||
class Meta:
|
||||
fields = (
|
||||
'label', 'device_name', 'mode', 'resolution', 'source',
|
||||
'adf_mode', 'enabled'
|
||||
)
|
||||
model = SaneScanner
|
||||
|
||||
|
||||
class WebFormSetupForm(forms.ModelForm):
|
||||
class Meta:
|
||||
fields = ('label', 'enabled', 'uncompress')
|
||||
|
||||
@@ -10,7 +10,7 @@ from navigation import Link
|
||||
|
||||
from .literals import (
|
||||
SOURCE_CHOICE_WEB_FORM, SOURCE_CHOICE_EMAIL_IMAP, SOURCE_CHOICE_EMAIL_POP3,
|
||||
SOURCE_CHOICE_STAGING, SOURCE_CHOICE_WATCH
|
||||
SOURCE_CHOICE_SANE_SCANNER, SOURCE_CHOICE_STAGING, SOURCE_CHOICE_WATCH
|
||||
)
|
||||
from .permissions import (
|
||||
permission_sources_setup_create, permission_sources_setup_delete,
|
||||
@@ -59,6 +59,11 @@ link_setup_source_create_webform = Link(
|
||||
text=_('Add new webform source'), view='sources:setup_source_create',
|
||||
args='"%s"' % SOURCE_CHOICE_WEB_FORM
|
||||
)
|
||||
link_setup_source_create_sane_scanner = Link(
|
||||
permissions=(permission_sources_setup_create,),
|
||||
text=_('Add new SANE scanner'), view='sources:setup_source_create',
|
||||
args='"%s"' % SOURCE_CHOICE_SANE_SCANNER
|
||||
)
|
||||
link_setup_source_delete = Link(
|
||||
permissions=(permission_sources_setup_delete,), tags='dangerous',
|
||||
text=_('Delete'), view='sources:setup_source_delete',
|
||||
|
||||
@@ -2,6 +2,32 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
SCANNER_SOURCE_FLATBED = 'flatbed'
|
||||
SCANNER_SOURCE_ADF = 'Automatic Document Feeder'
|
||||
|
||||
SCANNER_SOURCE_CHOICES = (
|
||||
(SCANNER_SOURCE_FLATBED, _('Flatbed')),
|
||||
(SCANNER_SOURCE_ADF, _('Document feeder')),
|
||||
)
|
||||
|
||||
SCANNER_ADF_MODE_SIMPLEX = 'simplex'
|
||||
SCANNER_ADF_MODE_DUPLEX = 'duplex'
|
||||
|
||||
SCANNER_ADF_MODE_CHOICES = (
|
||||
(SCANNER_ADF_MODE_SIMPLEX, _('Simplex')),
|
||||
(SCANNER_ADF_MODE_DUPLEX, _('Duplex')),
|
||||
)
|
||||
|
||||
SCANNER_MODE_LINEART = 'lineart'
|
||||
SCANNER_MODE_MONOCHROME = 'monochrome'
|
||||
SCANNER_MODE_COLOR = 'color'
|
||||
|
||||
SCANNER_MODE_CHOICES = (
|
||||
(SCANNER_MODE_LINEART, _('Lineart')),
|
||||
(SCANNER_MODE_MONOCHROME, _('Monochrome')),
|
||||
(SCANNER_MODE_COLOR, _('Color'))
|
||||
)
|
||||
|
||||
SOURCE_UNCOMPRESS_CHOICE_Y = 'y'
|
||||
SOURCE_UNCOMPRESS_CHOICE_N = 'n'
|
||||
SOURCE_UNCOMPRESS_CHOICE_ASK = 'a'
|
||||
@@ -22,8 +48,10 @@ SOURCE_CHOICE_STAGING = 'staging'
|
||||
SOURCE_CHOICE_WATCH = 'watch'
|
||||
SOURCE_CHOICE_EMAIL_POP3 = 'pop3'
|
||||
SOURCE_CHOICE_EMAIL_IMAP = 'imap'
|
||||
SOURCE_CHOICE_SANE_SCANNER = 'sane'
|
||||
|
||||
SOURCE_CHOICES = (
|
||||
(SOURCE_CHOICE_SANE_SCANNER, _('Scanner')),
|
||||
(SOURCE_CHOICE_WEB_FORM, _('Web form')),
|
||||
(SOURCE_CHOICE_STAGING, _('Staging folder')),
|
||||
(SOURCE_CHOICE_WATCH, _('Watch folder')),
|
||||
|
||||
28
mayan/apps/sources/migrations/0011_sanescanner.py
Normal file
28
mayan/apps/sources/migrations/0011_sanescanner.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-02-05 04:19
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sources', '0010_auto_20151001_0055'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SaneScanner',
|
||||
fields=[
|
||||
('interactivesource_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sources.InteractiveSource')),
|
||||
('device_name', models.CharField(help_text='Device name as returned by the SANE backend.', max_length=255, verbose_name='Device name')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'SANE Scanner',
|
||||
'verbose_name_plural': 'SANE Scanners',
|
||||
},
|
||||
bases=('sources.interactivesource',),
|
||||
),
|
||||
]
|
||||
25
mayan/apps/sources/migrations/0012_auto_20170205_0743.py
Normal file
25
mayan/apps/sources/migrations/0012_auto_20170205_0743.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-02-05 07:43
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sources', '0011_sanescanner'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='sanescanner',
|
||||
name='mode',
|
||||
field=models.CharField(choices=[('lineart', 'Lineart'), ('monochrome', 'Monochrome'), ('color', 'Color')], default='color', max_length=16, verbose_name='Mode'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sanescanner',
|
||||
name='resolution',
|
||||
field=models.PositiveIntegerField(default=300, help_text='Sets the resolution of the scanned image in DPI (dots per inch).', verbose_name='Resolution'),
|
||||
),
|
||||
]
|
||||
35
mayan/apps/sources/migrations/0013_auto_20170206_0710.py
Normal file
35
mayan/apps/sources/migrations/0013_auto_20170206_0710.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-02-06 07:10
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sources', '0012_auto_20170205_0743'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='sanescanner',
|
||||
name='adf_mode',
|
||||
field=models.CharField(blank=True, choices=[('simplex', 'Simples'), ('duplex', 'Duplex')], default='simplex', help_text='Selects the document feeder mode (simplex/duplex). If this option is not supported by your scanner, leave it blank.', max_length=16, verbose_name='ADF mode'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sanescanner',
|
||||
name='source',
|
||||
field=models.CharField(blank=True, choices=[('flatbed', 'Flatbed'), ('document-feeder', 'Document feeder')], default='flatbed', help_text='Selects the scan source (such as a document-feeder). If this option is not supported by your scanner, leave it blank.', max_length=16, verbose_name='Paper source'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sanescanner',
|
||||
name='mode',
|
||||
field=models.CharField(blank=True, choices=[('lineart', 'Lineart'), ('monochrome', 'Monochrome'), ('color', 'Color')], default='color', help_text='Selects the scan mode (e.g., lineart, monochrome, or color). If this option is not supported by your scanner, leave it blank.', max_length=16, verbose_name='Mode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sanescanner',
|
||||
name='resolution',
|
||||
field=models.PositiveIntegerField(blank=True, help_text='Sets the resolution of the scanned image in DPI (dots per inch). Typical value is 200. If this option is not supported by your scanner, leave it blank.', verbose_name='Resolution'),
|
||||
),
|
||||
]
|
||||
25
mayan/apps/sources/migrations/0014_auto_20170206_0722.py
Normal file
25
mayan/apps/sources/migrations/0014_auto_20170206_0722.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-02-06 07:22
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sources', '0013_auto_20170206_0710'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='sanescanner',
|
||||
name='adf_mode',
|
||||
field=models.CharField(blank=True, choices=[('simplex', 'Simplex'), ('duplex', 'Duplex')], default='simplex', help_text='Selects the document feeder mode (simplex/duplex). If this option is not supported by your scanner, leave it blank.', max_length=16, verbose_name='ADF mode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sanescanner',
|
||||
name='source',
|
||||
field=models.CharField(blank=True, choices=[('flatbed', 'Flatbed'), ('"Automatic Document Feeder"', 'Document feeder')], default='flatbed', help_text='Selects the scan source (such as a document-feeder). If this option is not supported by your scanner, leave it blank.', max_length=32, verbose_name='Paper source'),
|
||||
),
|
||||
]
|
||||
20
mayan/apps/sources/migrations/0015_auto_20170206_0835.py
Normal file
20
mayan/apps/sources/migrations/0015_auto_20170206_0835.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-02-06 08:35
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sources', '0014_auto_20170206_0722'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='sanescanner',
|
||||
name='source',
|
||||
field=models.CharField(blank=True, choices=[('flatbed', 'Flatbed'), ('Automatic Document Feeder', 'Document feeder')], default='flatbed', help_text='Selects the scan source (such as a document-feeder). If this option is not supported by your scanner, leave it blank.', max_length=32, verbose_name='Paper source'),
|
||||
),
|
||||
]
|
||||
@@ -8,19 +8,28 @@ import json
|
||||
import logging
|
||||
import os
|
||||
import poplib
|
||||
import subprocess
|
||||
|
||||
import sh
|
||||
import yaml
|
||||
|
||||
try:
|
||||
scanimage = sh.Command('/usr/bin/scanimage')
|
||||
except sh.CommandNotFound:
|
||||
scanimage = None
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.files import File
|
||||
from django.core.files.base import ContentFile
|
||||
from django.db import models, transaction
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from model_utils.managers import InheritanceManager
|
||||
|
||||
from common.compressed_files import CompressedFile, NotACompressedFile
|
||||
from common.utils import TemporaryFile
|
||||
from converter.literals import DIMENSION_SEPARATOR
|
||||
from converter.models import Transformation
|
||||
from djcelery.models import PeriodicTask, IntervalSchedule
|
||||
@@ -30,15 +39,20 @@ from metadata.api import save_metadata_list, set_bulk_metadata
|
||||
from metadata.models import MetadataType
|
||||
from tags.models import Tag
|
||||
|
||||
from .classes import Attachment, SourceUploadedFile, StagingFile
|
||||
from .classes import Attachment, PseudoFile, SourceUploadedFile, StagingFile
|
||||
from .exceptions import SourceException
|
||||
from .literals import (
|
||||
DEFAULT_INTERVAL, DEFAULT_POP3_TIMEOUT, DEFAULT_IMAP_MAILBOX,
|
||||
DEFAULT_METADATA_ATTACHMENT_NAME, SOURCE_CHOICES, SOURCE_CHOICE_STAGING,
|
||||
SOURCE_CHOICE_WATCH, SOURCE_CHOICE_WEB_FORM,
|
||||
SOURCE_INTERACTIVE_UNCOMPRESS_CHOICES, SOURCE_UNCOMPRESS_CHOICES,
|
||||
SOURCE_UNCOMPRESS_CHOICE_N, SOURCE_UNCOMPRESS_CHOICE_Y,
|
||||
SOURCE_CHOICE_EMAIL_IMAP, SOURCE_CHOICE_EMAIL_POP3
|
||||
DEFAULT_METADATA_ATTACHMENT_NAME, SCANNER_ADF_MODE_CHOICES,
|
||||
SCANNER_ADF_MODE_SIMPLEX, SCANNER_MODE_COLOR, SCANNER_MODE_CHOICES,
|
||||
SCANNER_SOURCE_CHOICES, SCANNER_SOURCE_FLATBED,
|
||||
SOURCE_CHOICES, SOURCE_CHOICE_STAGING, SOURCE_CHOICE_WATCH,
|
||||
SOURCE_CHOICE_WEB_FORM, SOURCE_INTERACTIVE_UNCOMPRESS_CHOICES,
|
||||
SOURCE_UNCOMPRESS_CHOICES, SOURCE_UNCOMPRESS_CHOICE_N,
|
||||
SOURCE_UNCOMPRESS_CHOICE_Y, SOURCE_CHOICE_EMAIL_IMAP,
|
||||
SOURCE_CHOICE_EMAIL_POP3, SOURCE_CHOICE_SANE_SCANNER,
|
||||
)
|
||||
from .settings import setting_scanimage_path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -153,6 +167,106 @@ class InteractiveSource(Source):
|
||||
verbose_name_plural = _('Interactive sources')
|
||||
|
||||
|
||||
class SaneScanner(InteractiveSource):
|
||||
can_compress = False
|
||||
is_interactive = True
|
||||
source_type = SOURCE_CHOICE_SANE_SCANNER
|
||||
|
||||
device_name = models.CharField(
|
||||
max_length=255,
|
||||
help_text=_('Device name as returned by the SANE backend.'),
|
||||
verbose_name=_('Device name')
|
||||
)
|
||||
mode = models.CharField(
|
||||
blank=True, choices=SCANNER_MODE_CHOICES, default=SCANNER_MODE_COLOR,
|
||||
help_text=_(
|
||||
'Selects the scan mode (e.g., lineart, monochrome, or color). '
|
||||
'If this option is not supported by your scanner, leave it blank.'
|
||||
), max_length=16, verbose_name=_('Mode')
|
||||
)
|
||||
resolution = models.PositiveIntegerField(
|
||||
blank=True, help_text=_(
|
||||
'Sets the resolution of the scanned image in DPI (dots per inch). '
|
||||
'Typical value is 200. If this option is not supported by your '
|
||||
'scanner, leave it blank.'
|
||||
), verbose_name=_('Resolution')
|
||||
)
|
||||
source = models.CharField(
|
||||
blank=True, choices=SCANNER_SOURCE_CHOICES,
|
||||
default=SCANNER_SOURCE_FLATBED, help_text=_(
|
||||
'Selects the scan source (such as a document-feeder). If this '
|
||||
'option is not supported by your scanner, leave it blank.'
|
||||
), max_length=32,
|
||||
verbose_name=_('Paper source')
|
||||
)
|
||||
adf_mode = models.CharField(
|
||||
blank=True, choices=SCANNER_ADF_MODE_CHOICES,
|
||||
default=SCANNER_ADF_MODE_SIMPLEX, help_text=_(
|
||||
'Selects the document feeder mode (simplex/duplex). If this '
|
||||
'option is not supported by your scanner, leave it blank.'
|
||||
), max_length=16, verbose_name=_('ADF mode')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('SANE Scanner')
|
||||
verbose_name_plural = _('SANE Scanners')
|
||||
|
||||
def clean_up_upload_file(self, upload_file_object):
|
||||
pass
|
||||
|
||||
def get_upload_file_object(self, form_data):
|
||||
temporary_file_object = TemporaryFile()
|
||||
command_line = [
|
||||
setting_scanimage_path.value, '-d', self.device_name,
|
||||
'--format', 'tiff',
|
||||
]
|
||||
|
||||
if self.resolution:
|
||||
command_line.extend(
|
||||
['--resolution', '{}'.format(self.resolution)]
|
||||
)
|
||||
|
||||
if self.mode:
|
||||
command_line.extend(
|
||||
['--mode', self.mode]
|
||||
)
|
||||
|
||||
if self.source:
|
||||
command_line.extend(
|
||||
['--source', self.source]
|
||||
)
|
||||
|
||||
if self.adf_mode:
|
||||
command_line.extend(
|
||||
['--adf-mode', self.adf_mode]
|
||||
)
|
||||
|
||||
filestderr = TemporaryFile()
|
||||
|
||||
try:
|
||||
logger.debug('Scan command line: %s', command_line)
|
||||
subprocess.check_call(
|
||||
command_line, stdout=temporary_file_object, stderr=filestderr
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
filestderr.seek(0)
|
||||
error_message = filestderr.read()
|
||||
logger.error(
|
||||
'Exception while scanning from source:%s ; %s', self,
|
||||
error_message
|
||||
)
|
||||
|
||||
message = _('Error while scanning; %s') % error_message
|
||||
self.logs.create(message=message)
|
||||
raise SourceException(message)
|
||||
else:
|
||||
return SourceUploadedFile(
|
||||
source=self, file=PseudoFile(
|
||||
file=temporary_file_object, name='scan {}'.format(now())
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class StagingFolderSource(InteractiveSource):
|
||||
"""
|
||||
The Staging folder source is interactive but instead of displaying an
|
||||
@@ -169,6 +283,7 @@ class StagingFolderSource(InteractiveSource):
|
||||
convert the scanned files into Mayan EDMS documents. Staging folders are
|
||||
useful when many users share a few networked scanners.
|
||||
"""
|
||||
can_compress = True
|
||||
is_interactive = True
|
||||
source_type = SOURCE_CHOICE_STAGING
|
||||
|
||||
@@ -257,6 +372,7 @@ class WebFormSource(InteractiveSource):
|
||||
documents from their own computers such as when each user has their own
|
||||
scanner.
|
||||
"""
|
||||
can_compress = True
|
||||
is_interactive = True
|
||||
source_type = SOURCE_CHOICE_WEB_FORM
|
||||
|
||||
|
||||
15
mayan/apps/sources/settings.py
Normal file
15
mayan/apps/sources/settings.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from smart_settings import Namespace
|
||||
|
||||
namespace = Namespace(name='sources', label=_('Sources'))
|
||||
|
||||
setting_scanimage_path = namespace.add_setting(
|
||||
global_name='SOURCE_SCANIMAGE_PATH', default='/usr/bin/scanimage',
|
||||
help_text=_(
|
||||
'File path to the scanimage program used to control image scanners.'
|
||||
),
|
||||
is_path=True
|
||||
)
|
||||
@@ -1,14 +1,16 @@
|
||||
from .forms import (
|
||||
POP3EmailSetupForm, IMAPEmailSetupForm,
|
||||
StagingFolderSetupForm, StagingUploadForm, WatchFolderSetupForm,
|
||||
WebFormSetupForm, WebFormUploadForm
|
||||
POP3EmailSetupForm, IMAPEmailSetupForm, SaneScannerSetupForm,
|
||||
SaneScannerUploadForm, StagingFolderSetupForm, StagingUploadForm,
|
||||
WatchFolderSetupForm, WebFormSetupForm, WebFormUploadForm
|
||||
)
|
||||
from .literals import (
|
||||
SOURCE_CHOICE_EMAIL_IMAP, SOURCE_CHOICE_EMAIL_POP3, SOURCE_CHOICE_STAGING,
|
||||
SOURCE_CHOICE_WATCH, SOURCE_CHOICE_WEB_FORM
|
||||
SOURCE_CHOICE_EMAIL_IMAP, SOURCE_CHOICE_EMAIL_POP3,
|
||||
SOURCE_CHOICE_SANE_SCANNER, SOURCE_CHOICE_STAGING, SOURCE_CHOICE_WATCH,
|
||||
SOURCE_CHOICE_WEB_FORM
|
||||
)
|
||||
from .models import (
|
||||
IMAPEmail, POP3Email, StagingFolderSource, WatchFolderSource, WebFormSource
|
||||
IMAPEmail, POP3Email, SaneScanner, StagingFolderSource, WatchFolderSource,
|
||||
WebFormSource
|
||||
)
|
||||
|
||||
|
||||
@@ -23,6 +25,8 @@ def get_class(source_type):
|
||||
return POP3Email
|
||||
elif source_type == SOURCE_CHOICE_EMAIL_IMAP:
|
||||
return IMAPEmail
|
||||
elif source_type == SOURCE_CHOICE_SANE_SCANNER:
|
||||
return SaneScanner
|
||||
|
||||
|
||||
def get_form_class(source_type):
|
||||
@@ -36,6 +40,8 @@ def get_form_class(source_type):
|
||||
return POP3EmailSetupForm
|
||||
elif source_type == SOURCE_CHOICE_EMAIL_IMAP:
|
||||
return IMAPEmailSetupForm
|
||||
elif source_type == SOURCE_CHOICE_SANE_SCANNER:
|
||||
return SaneScannerSetupForm
|
||||
|
||||
|
||||
def get_upload_form_class(source_type):
|
||||
@@ -43,3 +49,5 @@ def get_upload_form_class(source_type):
|
||||
return WebFormUploadForm
|
||||
elif source_type == SOURCE_CHOICE_STAGING:
|
||||
return StagingUploadForm
|
||||
elif source_type == SOURCE_CHOICE_SANE_SCANNER:
|
||||
return SaneScannerUploadForm
|
||||
|
||||
@@ -24,17 +24,13 @@ from documents.tasks import task_upload_new_version
|
||||
from metadata.api import decode_metadata_from_url
|
||||
from navigation import Link
|
||||
|
||||
from .exceptions import SourceException
|
||||
from .forms import (
|
||||
NewDocumentForm, NewVersionForm, WebFormUploadForm,
|
||||
WebFormUploadFormHTML5
|
||||
)
|
||||
from .literals import (
|
||||
SOURCE_CHOICE_STAGING, SOURCE_CHOICE_WEB_FORM,
|
||||
SOURCE_UNCOMPRESS_CHOICE_ASK,
|
||||
SOURCE_UNCOMPRESS_CHOICE_Y
|
||||
NewDocumentForm, NewVersionForm, WebFormUploadForm, WebFormUploadFormHTML5
|
||||
)
|
||||
from .literals import SOURCE_UNCOMPRESS_CHOICE_ASK, SOURCE_UNCOMPRESS_CHOICE_Y
|
||||
from .models import (
|
||||
InteractiveSource, Source, StagingFolderSource, WebFormSource
|
||||
InteractiveSource, Source, SaneScanner, StagingFolderSource
|
||||
)
|
||||
from .permissions import (
|
||||
permission_sources_setup_create, permission_sources_setup_delete,
|
||||
@@ -88,27 +84,10 @@ class UploadBaseView(MultiFormView):
|
||||
|
||||
@staticmethod
|
||||
def get_active_tab_links(document=None):
|
||||
tab_links = []
|
||||
|
||||
web_forms = WebFormSource.objects.filter(enabled=True)
|
||||
for web_form in web_forms:
|
||||
tab_links.append(
|
||||
UploadBaseView.get_tab_link_for_source(web_form, document)
|
||||
)
|
||||
|
||||
staging_folders = StagingFolderSource.objects.filter(enabled=True)
|
||||
for staging_folder in staging_folders:
|
||||
tab_links.append(
|
||||
UploadBaseView.get_tab_link_for_source(
|
||||
staging_folder, document
|
||||
)
|
||||
)
|
||||
|
||||
return {
|
||||
'tab_links': tab_links,
|
||||
SOURCE_CHOICE_WEB_FORM: web_forms,
|
||||
SOURCE_CHOICE_STAGING: staging_folders,
|
||||
}
|
||||
return [
|
||||
UploadBaseView.get_tab_link_for_source(source, document)
|
||||
for source in InteractiveSource.objects.filter(enabled=True).select_subclasses()
|
||||
]
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if 'source_id' in kwargs:
|
||||
@@ -163,6 +142,16 @@ class UploadBaseView(MultiFormView):
|
||||
}
|
||||
},
|
||||
]
|
||||
elif isinstance(self.source, SaneScanner):
|
||||
subtemplates_list.append({
|
||||
'name': 'sources/upload_multiform_subtemplate.html',
|
||||
'context': {
|
||||
'forms': context['forms'],
|
||||
'is_multipart': True,
|
||||
'title': _('Document properties'),
|
||||
'submit_label': _('Scan'),
|
||||
},
|
||||
})
|
||||
else:
|
||||
subtemplates_list.append({
|
||||
'name': 'sources/upload_multiform_subtemplate.html',
|
||||
@@ -173,8 +162,8 @@ class UploadBaseView(MultiFormView):
|
||||
},
|
||||
})
|
||||
|
||||
menu_facet.bound_links['sources:upload_interactive'] = self.tab_links['tab_links']
|
||||
menu_facet.bound_links['sources:upload_version'] = self.tab_links['tab_links']
|
||||
menu_facet.bound_links['sources:upload_interactive'] = self.tab_links
|
||||
menu_facet.bound_links['sources:upload_version'] = self.tab_links
|
||||
|
||||
context.update({
|
||||
'subtemplates_list': subtemplates_list,
|
||||
@@ -206,57 +195,63 @@ class UploadInteractiveView(UploadBaseView):
|
||||
).dispatch(request, *args, **kwargs)
|
||||
|
||||
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
|
||||
if self.source.can_compress:
|
||||
if self.source.uncompress == SOURCE_UNCOMPRESS_CHOICE_ASK:
|
||||
expand = forms['source_form'].cleaned_data.get('expand')
|
||||
else:
|
||||
expand = False
|
||||
|
||||
uploaded_file = self.source.get_upload_file_object(
|
||||
forms['source_form'].cleaned_data
|
||||
)
|
||||
|
||||
shared_uploaded_file = SharedUploadedFile.objects.create(
|
||||
file=uploaded_file.file
|
||||
)
|
||||
|
||||
label = 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
|
||||
|
||||
if not self.request.user.is_anonymous():
|
||||
user_id = self.request.user.pk
|
||||
if self.source.uncompress == SOURCE_UNCOMPRESS_CHOICE_Y:
|
||||
expand = True
|
||||
else:
|
||||
expand = False
|
||||
else:
|
||||
user_id = None
|
||||
expand = False
|
||||
|
||||
try:
|
||||
self.source.clean_up_upload_file(uploaded_file)
|
||||
except Exception as exception:
|
||||
messages.error(self.request, exception)
|
||||
|
||||
task_source_handle_upload.apply_async(kwargs=dict(
|
||||
description=forms['document_form'].cleaned_data.get('description'),
|
||||
document_type_id=self.document_type.pk,
|
||||
expand=expand,
|
||||
label=label,
|
||||
language=forms['document_form'].cleaned_data.get('language'),
|
||||
metadata_dict_list=decode_metadata_from_url(self.request.GET),
|
||||
shared_uploaded_file_id=shared_uploaded_file.pk,
|
||||
source_id=self.source.pk,
|
||||
tag_ids=self.request.GET.getlist('tags'),
|
||||
user_id=user_id,
|
||||
))
|
||||
messages.success(
|
||||
self.request,
|
||||
_(
|
||||
'New document queued for uploaded and will be available '
|
||||
'shortly.'
|
||||
uploaded_file = self.source.get_upload_file_object(
|
||||
forms['source_form'].cleaned_data
|
||||
)
|
||||
except SourceException as exception:
|
||||
messages.error(self.request, exception)
|
||||
else:
|
||||
shared_uploaded_file = SharedUploadedFile.objects.create(
|
||||
file=uploaded_file.file
|
||||
)
|
||||
|
||||
label = 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
|
||||
|
||||
if not self.request.user.is_anonymous():
|
||||
user_id = self.request.user.pk
|
||||
else:
|
||||
user_id = None
|
||||
|
||||
try:
|
||||
self.source.clean_up_upload_file(uploaded_file)
|
||||
except Exception as exception:
|
||||
messages.error(self.request, exception)
|
||||
|
||||
task_source_handle_upload.apply_async(kwargs=dict(
|
||||
description=forms['document_form'].cleaned_data.get('description'),
|
||||
document_type_id=self.document_type.pk,
|
||||
expand=expand,
|
||||
label=label,
|
||||
language=forms['document_form'].cleaned_data.get('language'),
|
||||
metadata_dict_list=decode_metadata_from_url(self.request.GET),
|
||||
shared_uploaded_file_id=shared_uploaded_file.pk,
|
||||
source_id=self.source.pk,
|
||||
tag_ids=self.request.GET.getlist('tags'),
|
||||
user_id=user_id,
|
||||
))
|
||||
messages.success(
|
||||
self.request,
|
||||
_(
|
||||
'New document queued for uploaded and will be available '
|
||||
'shortly.'
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return HttpResponseRedirect(
|
||||
'{}?{}'.format(
|
||||
@@ -266,12 +261,15 @@ class UploadInteractiveView(UploadBaseView):
|
||||
)
|
||||
|
||||
def create_source_form_form(self, **kwargs):
|
||||
if hasattr(self.source, 'uncompress'):
|
||||
show_expand = self.source.uncompress == SOURCE_UNCOMPRESS_CHOICE_ASK
|
||||
else:
|
||||
show_expand = False
|
||||
|
||||
return self.get_form_classes()['source_form'](
|
||||
prefix=kwargs['prefix'],
|
||||
source=self.source,
|
||||
show_expand=(
|
||||
self.source.uncompress == SOURCE_UNCOMPRESS_CHOICE_ASK
|
||||
),
|
||||
show_expand=show_expand,
|
||||
data=kwargs.get('data', None),
|
||||
files=kwargs.get('files', None),
|
||||
)
|
||||
@@ -301,7 +299,7 @@ class UploadInteractiveView(UploadBaseView):
|
||||
context['title'] = _(
|
||||
'Upload a local document from source: %s'
|
||||
) % self.source.label
|
||||
if not isinstance(self.source, StagingFolderSource):
|
||||
if not isinstance(self.source, StagingFolderSource) and not isinstance(self.source, SaneScanner):
|
||||
context['subtemplates_list'][0]['context'].update(
|
||||
{
|
||||
'form_action': '{}?{}'.format(
|
||||
@@ -349,38 +347,42 @@ class UploadInteractiveVersionView(UploadBaseView):
|
||||
).dispatch(request, *args, **kwargs)
|
||||
|
||||
def forms_valid(self, forms):
|
||||
uploaded_file = self.source.get_upload_file_object(
|
||||
forms['source_form'].cleaned_data
|
||||
)
|
||||
|
||||
shared_uploaded_file = SharedUploadedFile.objects.create(
|
||||
file=uploaded_file.file
|
||||
)
|
||||
|
||||
try:
|
||||
self.source.clean_up_upload_file(uploaded_file)
|
||||
except Exception as exception:
|
||||
messages.error(self.request, exception)
|
||||
|
||||
if not self.request.user.is_anonymous():
|
||||
user_id = self.request.user.pk
|
||||
else:
|
||||
user_id = None
|
||||
|
||||
task_upload_new_version.apply_async(kwargs=dict(
|
||||
shared_uploaded_file_id=shared_uploaded_file.pk,
|
||||
document_id=self.document.pk,
|
||||
user_id=user_id,
|
||||
comment=forms['document_form'].cleaned_data.get('comment')
|
||||
))
|
||||
|
||||
messages.success(
|
||||
self.request,
|
||||
_(
|
||||
'New document version queued for uploaded and will be '
|
||||
'available shortly.'
|
||||
uploaded_file = self.source.get_upload_file_object(
|
||||
forms['source_form'].cleaned_data
|
||||
)
|
||||
)
|
||||
except SourceException as exception:
|
||||
messages.error(self.request, exception)
|
||||
else:
|
||||
shared_uploaded_file = SharedUploadedFile.objects.create(
|
||||
file=uploaded_file.file
|
||||
)
|
||||
|
||||
try:
|
||||
self.source.clean_up_upload_file(uploaded_file)
|
||||
except Exception as exception:
|
||||
messages.error(self.request, exception)
|
||||
|
||||
if not self.request.user.is_anonymous():
|
||||
user_id = self.request.user.pk
|
||||
else:
|
||||
user_id = None
|
||||
|
||||
task_upload_new_version.apply_async(kwargs=dict(
|
||||
shared_uploaded_file_id=shared_uploaded_file.pk,
|
||||
document_id=self.document.pk,
|
||||
user_id=user_id,
|
||||
comment=forms['document_form'].cleaned_data.get('comment')
|
||||
))
|
||||
|
||||
messages.success(
|
||||
self.request,
|
||||
_(
|
||||
'New document version queued for uploaded and will be '
|
||||
'available shortly.'
|
||||
)
|
||||
)
|
||||
|
||||
return HttpResponseRedirect(
|
||||
reverse(
|
||||
'documents:document_version_list', args=(self.document.pk,)
|
||||
|
||||
Reference in New Issue
Block a user