Files
mayan-edms/mayan/apps/sources/models/base.py
Roberto Rosario fbd2b43b5e Update the task_check_interval_source reference
GitLab issue #617. Thanks to Lukas Gill (@lukkigi) for
the report and debug information.

Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
2019-06-19 00:33:44 -04:00

259 lines
8.5 KiB
Python

from __future__ import unicode_literals
import json
import logging
from django.db import models, transaction
from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from djcelery.models import PeriodicTask, IntervalSchedule
from model_utils.managers import InheritanceManager
from mayan.apps.common.compressed_files import Archive
from mayan.apps.common.exceptions import NoMIMETypeMatch
from mayan.apps.converter.models import Transformation
from mayan.apps.documents.models import Document, DocumentType
from mayan.apps.documents.settings import setting_language
from ..literals import (
DEFAULT_INTERVAL, SOURCE_CHOICES, SOURCE_UNCOMPRESS_CHOICES
)
from ..wizards import WizardStep
logger = logging.getLogger(__name__)
@python_2_unicode_compatible
class Source(models.Model):
label = models.CharField(
db_index=True, max_length=64, unique=True, verbose_name=_('Label')
)
enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
objects = InheritanceManager()
class Meta:
ordering = ('label',)
verbose_name = _('Source')
verbose_name_plural = _('Sources')
def __str__(self):
return '%s' % self.label
@classmethod
def class_fullname(cls):
return force_text(dict(SOURCE_CHOICES).get(cls.source_type))
def clean_up_upload_file(self, upload_file_object):
pass
# TODO: Should raise NotImplementedError?
def fullname(self):
return ' '.join([self.class_fullname(), '"%s"' % self.label])
def handle_upload(
self, file_object, description=None, document_type=None, expand=False,
label=None, language=None, user=None
):
"""
Handle an upload request from a file object which may be an individual
document or a compressed file containing multiple documents.
"""
documents = []
if not document_type:
document_type = self.document_type
kwargs = {
'description': description, 'document_type': document_type,
'label': label, 'language': language, 'user': user
}
if expand:
try:
compressed_file = Archive.open(file_object=file_object)
for compressed_file_child in compressed_file.members():
with compressed_file.open_member(filename=compressed_file_child) as file_object:
kwargs.update(
{'label': force_text(compressed_file_child)}
)
documents.append(
self.upload_document(
file_object=file_object, **kwargs
)
)
except NoMIMETypeMatch:
logging.debug(msg='Exception: NoMIMETypeMatch')
documents.append(
self.upload_document(file_object=file_object, **kwargs)
)
else:
documents.append(
self.upload_document(file_object=file_object, **kwargs)
)
# Return a list of newly created documents. Used by the email source
# to assign the from and subject metadata values.
return documents
def get_upload_file_object(self, form_data):
pass
# TODO: Should raise NotImplementedError?
def upload_document(
self, file_object, document_type, description=None, label=None,
language=None, querystring=None, user=None
):
"""
Upload an individual document
"""
try:
with transaction.atomic():
document = Document(
description=description or '', document_type=document_type,
label=label or file_object.name,
language=language or setting_language.value
)
document.save(_user=user)
except Exception as exception:
logger.critical(
'Unexpected exception while trying to create new document '
'"%s" from source "%s"; %s',
label or file_object.name, self, exception
)
raise
else:
try:
document_version = document.new_version(
file_object=file_object, _user=user,
)
if user:
document.add_as_recent_document_for_user(user=user)
Transformation.objects.copy(
source=self, targets=document_version.pages.all()
)
except Exception as exception:
logger.critical(
'Unexpected exception while trying to create version for '
'new document "%s" from source "%s"; %s',
label or file_object.name, self, exception, exc_info=True
)
document.delete(to_trash=False)
raise
else:
WizardStep.post_upload_process(
document=document, querystring=querystring
)
return document
class InteractiveSource(Source):
objects = InheritanceManager()
class Meta:
verbose_name = _('Interactive source')
verbose_name_plural = _('Interactive sources')
class OutOfProcessSource(Source):
is_interactive = False
objects = models.Manager()
class Meta:
verbose_name = _('Out of process')
verbose_name_plural = _('Out of process')
class IntervalBaseModel(OutOfProcessSource):
interval = models.PositiveIntegerField(
default=DEFAULT_INTERVAL,
help_text=_('Interval in seconds between checks for new documents.'),
verbose_name=_('Interval')
)
document_type = models.ForeignKey(
DocumentType,
help_text=_(
'Assign a document type to documents uploaded from this source.'
), on_delete=models.CASCADE,
verbose_name=_('Document type')
)
uncompress = models.CharField(
choices=SOURCE_UNCOMPRESS_CHOICES,
help_text=_('Whether to expand or not, compressed archives.'),
max_length=1, verbose_name=_('Uncompress')
)
objects = models.Manager()
class Meta:
verbose_name = _('Interval source')
verbose_name_plural = _('Interval sources')
def _delete_periodic_task(self, pk=None):
try:
periodic_task = PeriodicTask.objects.get(
name=self._get_periodic_task_name(pk)
)
interval_instance = periodic_task.interval
if tuple(interval_instance.periodictask_set.values_list('id', flat=True)) == (periodic_task.pk,):
# Only delete the interval if nobody else is using it
interval_instance.delete()
else:
periodic_task.delete()
except PeriodicTask.DoesNotExist:
logger.warning(
'Tried to delete non existant periodic task "%s"',
self._get_periodic_task_name(pk)
)
def _get_periodic_task_name(self, pk=None):
return 'check_interval_source-%i' % (pk or self.pk)
def delete(self, *args, **kwargs):
pk = self.pk
super(IntervalBaseModel, self).delete(*args, **kwargs)
self._delete_periodic_task(pk)
def save(self, *args, **kwargs):
new_source = not self.pk
super(IntervalBaseModel, self).save(*args, **kwargs)
if not new_source:
self._delete_periodic_task()
interval_instance, created = IntervalSchedule.objects.get_or_create(
every=self.interval, period='seconds'
)
# Create a new interval or reuse someone else's
PeriodicTask.objects.create(
name=self._get_periodic_task_name(),
interval=interval_instance,
task='mayan.apps.sources.tasks.task_check_interval_source',
kwargs=json.dumps({'source_id': self.pk})
)
class SourceLog(models.Model):
source = models.ForeignKey(
on_delete=models.CASCADE, related_name='logs', to=Source,
verbose_name=_('Source')
)
datetime = models.DateTimeField(
auto_now_add=True, editable=False, verbose_name=_('Date time')
)
message = models.TextField(
blank=True, editable=False, verbose_name=_('Message')
)
class Meta:
get_latest_by = 'datetime'
ordering = ('-datetime',)
verbose_name = _('Log entry')
verbose_name_plural = _('Log entries')