Documents now have their own dedicated DocumentPage submodel. The old DocumentPage is now called DocumentVersionPage. This allows mappings between document pages and document version pages, allowing renumbering, appending pages. DocumentPages have a content_object to map them to any other object. For now they only map to DocumentVersionPages. New option added to the version upload form to append the pages of the new version. A new view was added to just append new pages with wraps the new document version upload form and hides the append pages checkbox set to True. Add a new action, reset_pages to reset the pages of the document to those of the latest version. Missing: appending tests, checks for proper content_object in OCR and document parsing. Author: Roberto Rosario <roberto.rosario@mayan-edms.com> Date: Thu Oct 11 12:00:25 2019 -0400
275 lines
8.8 KiB
Python
275 lines
8.8 KiB
Python
from __future__ import absolute_import, unicode_literals
|
|
|
|
import logging
|
|
import uuid
|
|
|
|
from django.apps import apps
|
|
from django.core.files import File
|
|
from django.db import models, transaction
|
|
from django.urls import reverse
|
|
from django.utils.encoding import python_2_unicode_compatible
|
|
from django.utils.functional import cached_property
|
|
from django.utils.timezone import now
|
|
from django.utils.translation import ugettext, ugettext_lazy as _
|
|
|
|
from ..events import (
|
|
event_document_create, event_document_properties_edit,
|
|
event_document_type_change,
|
|
)
|
|
from ..literals import DOCUMENT_IMAGES_CACHE_NAME
|
|
from ..managers import DocumentManager, PassthroughManager, TrashCanManager
|
|
from ..settings import setting_language
|
|
from ..signals import post_document_type_change
|
|
|
|
from .document_type_models import DocumentType
|
|
|
|
__all__ = ('Document',)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@python_2_unicode_compatible
|
|
class Document(models.Model):
|
|
"""
|
|
Defines a single document with it's fields and properties
|
|
Fields:
|
|
* uuid - UUID of a document, universally Unique ID. An unique identifier
|
|
generated for each document. No two documents can ever have the same UUID.
|
|
This ID is generated automatically.
|
|
"""
|
|
uuid = models.UUIDField(
|
|
default=uuid.uuid4, editable=False, help_text=_(
|
|
'UUID of a document, universally Unique ID. An unique identifier '
|
|
'generated for each document.'
|
|
), verbose_name=_('UUID')
|
|
)
|
|
document_type = models.ForeignKey(
|
|
on_delete=models.CASCADE, related_name='documents', to=DocumentType,
|
|
verbose_name=_('Document type')
|
|
)
|
|
label = models.CharField(
|
|
blank=True, db_index=True, default='', max_length=255,
|
|
help_text=_('The name of the document.'), verbose_name=_('Label')
|
|
)
|
|
description = models.TextField(
|
|
blank=True, default='', help_text=_(
|
|
'An optional short text describing a document.'
|
|
), verbose_name=_('Description')
|
|
)
|
|
date_added = models.DateTimeField(
|
|
auto_now_add=True, db_index=True, help_text=_(
|
|
'The server date and time when the document was finally '
|
|
'processed and added to the system.'
|
|
), verbose_name=_('Added')
|
|
)
|
|
language = models.CharField(
|
|
blank=True, default=setting_language.value, help_text=_(
|
|
'The dominant language in the document.'
|
|
), max_length=8, verbose_name=_('Language')
|
|
)
|
|
in_trash = models.BooleanField(
|
|
db_index=True, default=False, help_text=_(
|
|
'Whether or not this document is in the trash.'
|
|
), editable=False, verbose_name=_('In trash?')
|
|
)
|
|
# TODO: set editable to False
|
|
deleted_date_time = models.DateTimeField(
|
|
blank=True, editable=True, help_text=_(
|
|
'The server date and time when the document was moved to the '
|
|
'trash.'
|
|
), null=True, verbose_name=_('Date and time trashed')
|
|
)
|
|
is_stub = models.BooleanField(
|
|
db_index=True, default=True, editable=False, help_text=_(
|
|
'A document stub is a document with an entry on the database but '
|
|
'no file uploaded. This could be an interrupted upload or a '
|
|
'deferred upload via the API.'
|
|
), verbose_name=_('Is stub?')
|
|
)
|
|
|
|
objects = DocumentManager()
|
|
passthrough = PassthroughManager()
|
|
trash = TrashCanManager()
|
|
|
|
class Meta:
|
|
ordering = ('label',)
|
|
verbose_name = _('Document')
|
|
verbose_name_plural = _('Documents')
|
|
|
|
def __str__(self):
|
|
return self.label or ugettext('Document stub, id: %d') % self.pk
|
|
|
|
def add_as_recent_document_for_user(self, user):
|
|
RecentDocument = apps.get_model(
|
|
app_label='documents', model_name='RecentDocument'
|
|
)
|
|
return RecentDocument.objects.add_document_for_user(user, self)
|
|
|
|
@cached_property
|
|
def cache(self):
|
|
Cache = apps.get_model(app_label='file_caching', model_name='Cache')
|
|
return Cache.objects.get(name=DOCUMENT_IMAGES_CACHE_NAME)
|
|
|
|
@cached_property
|
|
def cache_partition(self):
|
|
partition, created = self.cache.partitions.get_or_create(
|
|
name='document-{}'.format(self.uuid)
|
|
)
|
|
return partition
|
|
|
|
@property
|
|
def checksum(self):
|
|
return self.latest_version.checksum
|
|
|
|
@property
|
|
def date_updated(self):
|
|
return self.latest_version.timestamp
|
|
|
|
def delete(self, *args, **kwargs):
|
|
to_trash = kwargs.pop('to_trash', True)
|
|
|
|
if not self.in_trash and to_trash:
|
|
self.in_trash = True
|
|
self.deleted_date_time = now()
|
|
self.save()
|
|
else:
|
|
for version in self.versions.all():
|
|
version.delete()
|
|
|
|
return super(Document, self).delete(*args, **kwargs)
|
|
|
|
def exists(self):
|
|
"""
|
|
Returns a boolean value that indicates if the document's
|
|
latest version file exists in storage
|
|
"""
|
|
latest_version = self.latest_version
|
|
if latest_version:
|
|
return latest_version.exists()
|
|
else:
|
|
return False
|
|
|
|
@property
|
|
def file_mime_encoding(self):
|
|
return self.latest_version.encoding
|
|
|
|
@property
|
|
def file_mimetype(self):
|
|
return self.latest_version.mimetype
|
|
|
|
def get_absolute_url(self):
|
|
return reverse(
|
|
viewname='documents:document_preview', kwargs={'pk': self.pk}
|
|
)
|
|
|
|
def get_api_image_url(self, *args, **kwargs):
|
|
first_page = self.pages.first()
|
|
if first_page:
|
|
return first_page.get_api_image_url(*args, **kwargs)
|
|
|
|
@property
|
|
def is_in_trash(self):
|
|
return self.in_trash
|
|
|
|
@property
|
|
def latest_version(self):
|
|
return self.versions.order_by('timestamp').last()
|
|
|
|
def natural_key(self):
|
|
return (self.uuid,)
|
|
natural_key.dependencies = ['documents.DocumentType']
|
|
|
|
def new_version(self, file_object, append_pages=False, comment=None, _user=None):
|
|
logger.info('Creating new document version for document: %s', self)
|
|
DocumentVersion = apps.get_model(
|
|
app_label='documents', model_name='DocumentVersion'
|
|
)
|
|
|
|
document_version = DocumentVersion(
|
|
document=self, comment=comment or '', file=File(file_object)
|
|
)
|
|
document_version.save(append_pages=append_pages, _user=_user)
|
|
|
|
logger.info('New document version queued for document: %s', self)
|
|
|
|
return document_version
|
|
|
|
def open(self, *args, **kwargs):
|
|
"""
|
|
Return a file descriptor to a document's file irrespective of
|
|
the storage backend
|
|
"""
|
|
return self.latest_version.open(*args, **kwargs)
|
|
|
|
@property
|
|
def page_count(self):
|
|
return self.pages.count()
|
|
|
|
@property
|
|
def pages(self):
|
|
return self.pages.all()
|
|
|
|
@property
|
|
def pages_all(self):
|
|
DocumentPage = apps.get_model(
|
|
app_label='documents', model_name='DocumentPage'
|
|
)
|
|
return DocumentPage.passthrough.filter(document=self)
|
|
|
|
def pages_reset(self, update_page_count=True):
|
|
with transaction.atomic():
|
|
for page in self.pages.all():
|
|
page.delete()
|
|
|
|
if update_page_count:
|
|
self.latest_version.update_page_count()
|
|
|
|
for version_page in self.latest_version.pages.all():
|
|
document_page = self.pages.create(
|
|
content_object = version_page
|
|
)
|
|
|
|
def restore(self):
|
|
self.in_trash = False
|
|
self.save()
|
|
|
|
def save(self, *args, **kwargs):
|
|
user = kwargs.pop('_user', None)
|
|
_commit_events = kwargs.pop('_commit_events', True)
|
|
new_document = not self.pk
|
|
super(Document, self).save(*args, **kwargs)
|
|
|
|
if new_document:
|
|
if user:
|
|
self.add_as_recent_document_for_user(user)
|
|
event_document_create.commit(
|
|
actor=user, target=self, action_object=self.document_type
|
|
)
|
|
else:
|
|
event_document_create.commit(
|
|
target=self, action_object=self.document_type
|
|
)
|
|
else:
|
|
if _commit_events:
|
|
event_document_properties_edit.commit(actor=user, target=self)
|
|
|
|
def save_to_file(self, *args, **kwargs):
|
|
return self.latest_version.save_to_file(*args, **kwargs)
|
|
|
|
def set_document_type(self, document_type, force=False, _user=None):
|
|
has_changed = self.document_type != document_type
|
|
|
|
self.document_type = document_type
|
|
self.save()
|
|
if has_changed or force:
|
|
post_document_type_change.send(
|
|
sender=self.__class__, instance=self
|
|
)
|
|
|
|
event_document_type_change.commit(actor=_user, target=self)
|
|
if _user:
|
|
self.add_as_recent_document_for_user(user=_user)
|
|
|
|
@property
|
|
def size(self):
|
|
return self.latest_version.size
|