Initial commit to support page mapping
Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
This commit is contained in:
@@ -63,7 +63,7 @@ class DocumentParsingApp(MayanAppConfig):
|
|||||||
app_label='documents', model_name='Document'
|
app_label='documents', model_name='Document'
|
||||||
)
|
)
|
||||||
DocumentPage = apps.get_model(
|
DocumentPage = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentPage'
|
app_label='documents', model_name='DocumentVersionPage'
|
||||||
)
|
)
|
||||||
DocumentType = apps.get_model(
|
DocumentType = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentType'
|
app_label='documents', model_name='DocumentType'
|
||||||
@@ -100,9 +100,9 @@ class DocumentParsingApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
ModelField(
|
#ModelField(
|
||||||
model=Document, name='versions__version_pages__content__content'
|
# model=Document, name='versions__pages__content__content'
|
||||||
)
|
#)
|
||||||
|
|
||||||
ModelPermission.register(
|
ModelPermission.register(
|
||||||
model=Document, permissions=(
|
model=Document, permissions=(
|
||||||
@@ -133,7 +133,7 @@ class DocumentParsingApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
|
|
||||||
document_search.add_model_field(
|
document_search.add_model_field(
|
||||||
field='versions__version_pages__content__content', label=_('Content')
|
field='versions__pages__content__content', label=_('Content')
|
||||||
)
|
)
|
||||||
|
|
||||||
document_page_search.add_model_field(
|
document_page_search.add_model_field(
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ class DocumentPageContent(models.Model):
|
|||||||
This model store's the parsed content of a document page.
|
This model store's the parsed content of a document page.
|
||||||
"""
|
"""
|
||||||
document_page = models.OneToOneField(
|
document_page = models.OneToOneField(
|
||||||
on_delete=models.CASCADE, related_name='content', to=DocumentPage,
|
on_delete=models.CASCADE, related_name='content',
|
||||||
verbose_name=_('Document page')
|
to=DocumentPage, verbose_name=_('Document page')
|
||||||
)
|
)
|
||||||
content = models.TextField(
|
content = models.TextField(
|
||||||
blank=True, help_text=_(
|
blank=True, help_text=_(
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ from mayan.apps.common.generics import (
|
|||||||
)
|
)
|
||||||
from mayan.apps.common.mixins import ExternalObjectMixin
|
from mayan.apps.common.mixins import ExternalObjectMixin
|
||||||
from mayan.apps.documents.forms import DocumentTypeFilteredSelectForm
|
from mayan.apps.documents.forms import DocumentTypeFilteredSelectForm
|
||||||
from mayan.apps.documents.models import Document, DocumentPage, DocumentType
|
from mayan.apps.documents.models import (
|
||||||
|
Document, DocumentType, DocumentVersionPage
|
||||||
|
)
|
||||||
|
|
||||||
from .forms import DocumentContentForm, DocumentPageContentForm
|
from .forms import DocumentContentForm, DocumentPageContentForm
|
||||||
from .models import DocumentPageContent, DocumentVersionParseError
|
from .models import DocumentPageContent, DocumentVersionParseError
|
||||||
@@ -87,7 +89,7 @@ class DocumentContentDownloadView(SingleObjectDownloadView):
|
|||||||
|
|
||||||
class DocumentPageContentView(SingleObjectDetailView):
|
class DocumentPageContentView(SingleObjectDetailView):
|
||||||
form_class = DocumentPageContentForm
|
form_class = DocumentPageContentForm
|
||||||
model = DocumentPage
|
model = DocumentVersionPage
|
||||||
object_permission = permission_content_view
|
object_permission = permission_content_view
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
|||||||
@@ -3,18 +3,12 @@ from __future__ import unicode_literals
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
DeletedDocument, Document, DocumentPage, DocumentType,
|
DeletedDocument, Document, DocumentType, DocumentTypeFilename,
|
||||||
DocumentTypeFilename, DocumentVersion, DuplicatedDocument, RecentDocument
|
DocumentVersion, DocumentVersionPage, DuplicatedDocument,
|
||||||
|
RecentDocument
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DocumentPageInline(admin.StackedInline):
|
|
||||||
model = DocumentPage
|
|
||||||
extra = 1
|
|
||||||
classes = ('collapse-open',)
|
|
||||||
allow_add = True
|
|
||||||
|
|
||||||
|
|
||||||
class DocumentTypeFilenameInline(admin.StackedInline):
|
class DocumentTypeFilenameInline(admin.StackedInline):
|
||||||
model = DocumentTypeFilename
|
model = DocumentTypeFilename
|
||||||
extra = 1
|
extra = 1
|
||||||
@@ -29,6 +23,13 @@ class DocumentVersionInline(admin.StackedInline):
|
|||||||
allow_add = True
|
allow_add = True
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentVersionPageInline(admin.StackedInline):
|
||||||
|
model = DocumentVersionPage
|
||||||
|
extra = 1
|
||||||
|
classes = ('collapse-open',)
|
||||||
|
allow_add = True
|
||||||
|
|
||||||
|
|
||||||
@admin.register(DeletedDocument)
|
@admin.register(DeletedDocument)
|
||||||
class DeletedDocumentAdmin(admin.ModelAdmin):
|
class DeletedDocumentAdmin(admin.ModelAdmin):
|
||||||
date_hierarchy = 'deleted_date_time'
|
date_hierarchy = 'deleted_date_time'
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ from .serializers import (
|
|||||||
DocumentTypeSerializer, DocumentVersionSerializer,
|
DocumentTypeSerializer, DocumentVersionSerializer,
|
||||||
NewDocumentSerializer, NewDocumentVersionSerializer,
|
NewDocumentSerializer, NewDocumentVersionSerializer,
|
||||||
RecentDocumentSerializer, WritableDocumentSerializer,
|
RecentDocumentSerializer, WritableDocumentSerializer,
|
||||||
WritableDocumentTypeSerializer, WritableDocumentVersionSerializer
|
WritableDocumentTypeSerializer, WritableDocumentVersionSerializer,
|
||||||
|
DocumentVersionPageSerializer
|
||||||
)
|
)
|
||||||
from .settings import settings_document_page_image_cache_time
|
from .settings import settings_document_page_image_cache_time
|
||||||
from .tasks import task_generate_document_page_image
|
from .tasks import task_generate_document_page_image
|
||||||
@@ -168,13 +169,13 @@ class APIDocumentPageImageView(generics.RetrieveAPIView):
|
|||||||
)
|
)
|
||||||
return document
|
return document
|
||||||
|
|
||||||
def get_document_version(self):
|
#def get_document_version(self):
|
||||||
return get_object_or_404(
|
# return get_object_or_404(
|
||||||
self.get_document().versions.all(), pk=self.kwargs['version_pk']
|
# self.get_document().versions.all(), pk=self.kwargs['version_pk']
|
||||||
)
|
# )
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.get_document_version().pages_all.all()
|
return self.get_document().pages_all.all()
|
||||||
|
|
||||||
def get_serializer(self, *args, **kwargs):
|
def get_serializer(self, *args, **kwargs):
|
||||||
return None
|
return None
|
||||||
@@ -221,6 +222,95 @@ class APIDocumentPageImageView(generics.RetrieveAPIView):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class APIDocumentVersionPageImageView(generics.RetrieveAPIView):
|
||||||
|
"""
|
||||||
|
get: Returns an image representation of the selected document version page.
|
||||||
|
"""
|
||||||
|
lookup_url_kwarg = 'page_pk'
|
||||||
|
|
||||||
|
def get_document(self):
|
||||||
|
if self.request.method == 'GET':
|
||||||
|
permission_required = permission_document_view
|
||||||
|
else:
|
||||||
|
permission_required = permission_document_edit
|
||||||
|
|
||||||
|
document = get_object_or_404(Document.passthrough, pk=self.kwargs['pk'])
|
||||||
|
|
||||||
|
AccessControlList.objects.check_access(
|
||||||
|
obj=document, permissions=(permission_required,),
|
||||||
|
user=self.request.user
|
||||||
|
)
|
||||||
|
return document
|
||||||
|
|
||||||
|
def get_document_version(self):
|
||||||
|
return get_object_or_404(
|
||||||
|
self.get_document().versions.all(), pk=self.kwargs['version_pk']
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.get_document_version().pages_all.all()
|
||||||
|
|
||||||
|
def get_serializer(self, *args, **kwargs):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
@cache_control(private=True)
|
||||||
|
def retrieve(self, request, *args, **kwargs):
|
||||||
|
width = request.GET.get('width')
|
||||||
|
height = request.GET.get('height')
|
||||||
|
zoom = request.GET.get('zoom')
|
||||||
|
|
||||||
|
if zoom:
|
||||||
|
zoom = int(zoom)
|
||||||
|
|
||||||
|
rotation = request.GET.get('rotation')
|
||||||
|
|
||||||
|
if rotation:
|
||||||
|
rotation = int(rotation)
|
||||||
|
|
||||||
|
maximum_layer_order = request.GET.get('maximum_layer_order')
|
||||||
|
if maximum_layer_order:
|
||||||
|
maximum_layer_order = int(maximum_layer_order)
|
||||||
|
|
||||||
|
task = task_generate_document_version_page_image.apply_async(
|
||||||
|
kwargs=dict(
|
||||||
|
document_version_page_id=self.get_object().pk, width=width,
|
||||||
|
height=height, zoom=zoom, rotation=rotation,
|
||||||
|
maximum_layer_order=maximum_layer_order,
|
||||||
|
user_id=request.user.pk
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
cache_filename = task.get(timeout=DOCUMENT_IMAGE_TASK_TIMEOUT)
|
||||||
|
cache_file = self.get_object().cache_partition.get_file(filename=cache_filename)
|
||||||
|
with cache_file.open() as file_object:
|
||||||
|
response = HttpResponse(file_object.read(), content_type='image')
|
||||||
|
if '_hash' in request.GET:
|
||||||
|
patch_cache_control(
|
||||||
|
response=response,
|
||||||
|
max_age=settings_document_page_image_cache_time.value
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class APIDocumentPageListView(generics.ListAPIView):
|
||||||
|
serializer_class = DocumentPageSerializer
|
||||||
|
|
||||||
|
def get_document(self):
|
||||||
|
document = get_object_or_404(Document, pk=self.kwargs['pk'])
|
||||||
|
|
||||||
|
AccessControlList.objects.check_access(
|
||||||
|
obj=document, permissions=(permission_document_view,),
|
||||||
|
user=self.request.user
|
||||||
|
)
|
||||||
|
return document
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.get_document().pages.all()
|
||||||
|
|
||||||
|
|
||||||
class APIDocumentPageView(generics.RetrieveUpdateAPIView):
|
class APIDocumentPageView(generics.RetrieveUpdateAPIView):
|
||||||
"""
|
"""
|
||||||
get: Returns the selected document page details.
|
get: Returns the selected document page details.
|
||||||
@@ -230,6 +320,38 @@ class APIDocumentPageView(generics.RetrieveUpdateAPIView):
|
|||||||
lookup_url_kwarg = 'page_pk'
|
lookup_url_kwarg = 'page_pk'
|
||||||
serializer_class = DocumentPageSerializer
|
serializer_class = DocumentPageSerializer
|
||||||
|
|
||||||
|
def get_document(self):
|
||||||
|
if self.request.method == 'GET':
|
||||||
|
permission_required = permission_document_view
|
||||||
|
else:
|
||||||
|
permission_required = permission_document_edit
|
||||||
|
|
||||||
|
document = get_object_or_404(Document, pk=self.kwargs['pk'])
|
||||||
|
|
||||||
|
AccessControlList.objects.check_access(
|
||||||
|
obj=document, permissions=(permission_required,),
|
||||||
|
user=self.request.user
|
||||||
|
)
|
||||||
|
return document
|
||||||
|
|
||||||
|
#def get_document_version(self):
|
||||||
|
# return get_object_or_404(
|
||||||
|
# self.get_document().versions.all(), pk=self.kwargs['version_pk']
|
||||||
|
# )
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.get_document().pages.all()
|
||||||
|
|
||||||
|
|
||||||
|
class APIDocumentVersionPageView(generics.RetrieveUpdateAPIView):
|
||||||
|
"""
|
||||||
|
get: Returns the selected document verion page details.
|
||||||
|
patch: Edit the selected document version page.
|
||||||
|
put: Edit the selected document version page.
|
||||||
|
"""
|
||||||
|
lookup_url_kwarg = 'page_pk'
|
||||||
|
serializer_class = DocumentVersionPageSerializer
|
||||||
|
|
||||||
def get_document(self):
|
def get_document(self):
|
||||||
if self.request.method == 'GET':
|
if self.request.method == 'GET':
|
||||||
permission_required = permission_document_view
|
permission_required = permission_document_view
|
||||||
@@ -289,8 +411,7 @@ class APIDocumentTypeView(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
'GET': (permission_document_type_view,),
|
'GET': (permission_document_type_view,),
|
||||||
'PUT': (permission_document_type_edit,),
|
'PUT': (permission_document_type_edit,),
|
||||||
'PATCH': (permission_document_type_edit,),
|
'PATCH': (permission_document_type_edit,),
|
||||||
'DELETE': (permission_document_type_delete,)
|
'DELETE': (permission_document_type_delete,) }
|
||||||
}
|
|
||||||
permission_classes = (MayanPermission,)
|
permission_classes = (MayanPermission,)
|
||||||
queryset = DocumentType.objects.all()
|
queryset = DocumentType.objects.all()
|
||||||
|
|
||||||
|
|||||||
@@ -120,8 +120,8 @@ class DocumentsApp(MayanAppConfig):
|
|||||||
|
|
||||||
DeletedDocument = self.get_model(model_name='DeletedDocument')
|
DeletedDocument = self.get_model(model_name='DeletedDocument')
|
||||||
Document = self.get_model(model_name='Document')
|
Document = self.get_model(model_name='Document')
|
||||||
DocumentPage = self.get_model(model_name='DocumentPage')
|
DocumentPage = self.get_model(model_name='DocumentVersionPage')
|
||||||
DocumentPageResult = self.get_model(model_name='DocumentPageResult')
|
DocumentPageResult = self.get_model(model_name='DocumentVersionPageResult')
|
||||||
DocumentType = self.get_model(model_name='DocumentType')
|
DocumentType = self.get_model(model_name='DocumentType')
|
||||||
DocumentTypeFilename = self.get_model(model_name='DocumentTypeFilename')
|
DocumentTypeFilename = self.get_model(model_name='DocumentTypeFilename')
|
||||||
DocumentVersion = self.get_model(model_name='DocumentVersion')
|
DocumentVersion = self.get_model(model_name='DocumentVersion')
|
||||||
|
|||||||
@@ -32,12 +32,12 @@ class DashboardWidgetDocumentPagesTotal(DashboardWidgetNumeric):
|
|||||||
AccessControlList = apps.get_model(
|
AccessControlList = apps.get_model(
|
||||||
app_label='acls', model_name='AccessControlList'
|
app_label='acls', model_name='AccessControlList'
|
||||||
)
|
)
|
||||||
DocumentPage = apps.get_model(
|
DocumentVersionPage = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentPage'
|
app_label='documents', model_name='DocumentVersionPage'
|
||||||
)
|
)
|
||||||
self.count = AccessControlList.objects.restrict_queryset(
|
self.count = AccessControlList.objects.restrict_queryset(
|
||||||
permission=permission_document_view, user=request.user,
|
permission=permission_document_view, user=request.user,
|
||||||
queryset=DocumentPage.objects.all()
|
queryset=DocumentVersionPage.objects.all()
|
||||||
).count()
|
).count()
|
||||||
return super(DashboardWidgetDocumentPagesTotal, self).render(request)
|
return super(DashboardWidgetDocumentPagesTotal, self).render(request)
|
||||||
|
|
||||||
|
|||||||
@@ -42,3 +42,5 @@ PAGE_RANGE_RANGE = 'range'
|
|||||||
PAGE_RANGE_CHOICES = (
|
PAGE_RANGE_CHOICES = (
|
||||||
(PAGE_RANGE_ALL, _('All pages')), (PAGE_RANGE_RANGE, _('Page range'))
|
(PAGE_RANGE_ALL, _('All pages')), (PAGE_RANGE_RANGE, _('Page range'))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
RETRY_DELAY_DOCUMENT_RESET_PAGES = 30
|
||||||
|
|||||||
@@ -28,15 +28,15 @@ class DocumentManager(models.Manager):
|
|||||||
|
|
||||||
class DocumentPageManager(models.Manager):
|
class DocumentPageManager(models.Manager):
|
||||||
def get_by_natural_key(self, page_number, document_version_natural_key):
|
def get_by_natural_key(self, page_number, document_version_natural_key):
|
||||||
DocumentVersion = apps.get_model(
|
Document = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentVersion'
|
app_label='documents', model_name='Document'
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
document_version = DocumentVersion.objects.get_by_natural_key(*document_version_natural_key)
|
document = Document.objects.get_by_natural_key(*document_version_natural_key)
|
||||||
except DocumentVersion.DoesNotExist:
|
except Document.DoesNotExist:
|
||||||
raise self.model.DoesNotExist
|
raise self.model.DoesNotExist
|
||||||
|
|
||||||
return self.get(document_version__pk=document_version.pk, page_number=page_number)
|
return self.get(document__pk=document.pk, page_number=page_number)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return models.QuerySet(
|
return models.QuerySet(
|
||||||
@@ -124,6 +124,24 @@ class DocumentVersionManager(models.Manager):
|
|||||||
return self.get(document__pk=document.pk, checksum=checksum)
|
return self.get(document__pk=document.pk, checksum=checksum)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentVersionPageManager(models.Manager):
|
||||||
|
def get_by_natural_key(self, page_number, document_version_natural_key):
|
||||||
|
DocumentVersion = apps.get_model(
|
||||||
|
app_label='documents', model_name='DocumentVersion'
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
document_version = DocumentVersion.objects.get_by_natural_key(*document_version_natural_key)
|
||||||
|
except DocumentVersion.DoesNotExist:
|
||||||
|
raise self.model.DoesNotExist
|
||||||
|
|
||||||
|
return self.get(document_version__pk=document_version.pk, page_number=page_number)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return models.QuerySet(
|
||||||
|
model=self.model, using=self._db
|
||||||
|
).filter(enabled=True)
|
||||||
|
|
||||||
|
|
||||||
class DuplicatedDocumentManager(models.Manager):
|
class DuplicatedDocumentManager(models.Manager):
|
||||||
def clean_empty_duplicate_lists(self):
|
def clean_empty_duplicate_lists(self):
|
||||||
self.filter(documents=None).delete()
|
self.filter(documents=None).delete()
|
||||||
|
|||||||
27
mayan/apps/documents/migrations/0052_auto_20191007_1921.py
Normal file
27
mayan/apps/documents/migrations/0052_auto_20191007_1921.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('documents', '0051_documentpage_enabled'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='DocumentPageResult',
|
||||||
|
),
|
||||||
|
migrations.RenameModel('DocumentPage', 'DocumentVersionPage'),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='documentversionpage',
|
||||||
|
name='document_version',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name='version_pages', to='documents.DocumentVersion',
|
||||||
|
verbose_name='Document version'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
27
mayan/apps/documents/migrations/0053_auto_20191007_1922.py
Normal file
27
mayan/apps/documents/migrations/0053_auto_20191007_1922.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('documents', '0052_auto_20191007_1921'),
|
||||||
|
('ocr', '0008_auto_20180917_0646'),
|
||||||
|
('document_parsing', '0004_auto_20180917_0645'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DocumentPageResult',
|
||||||
|
fields=[
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Document version page',
|
||||||
|
'verbose_name_plural': 'Document version pages',
|
||||||
|
'ordering': ('document_version__document', 'page_number'),
|
||||||
|
'proxy': True,
|
||||||
|
'indexes': [],
|
||||||
|
},
|
||||||
|
bases=('documents.documentversionpage',),
|
||||||
|
),
|
||||||
|
]
|
||||||
31
mayan/apps/documents/migrations/0054_auto_20191008_1522.py
Normal file
31
mayan/apps/documents/migrations/0054_auto_20191008_1522.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.22 on 2019-10-08 15:22
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('documents', '0053_auto_20191007_1922'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='DocumentPageResult',
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DocumentVersionPageResult',
|
||||||
|
fields=[
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Document version page',
|
||||||
|
'verbose_name_plural': 'Document version pages',
|
||||||
|
'ordering': ('document_version__document', 'page_number'),
|
||||||
|
'proxy': True,
|
||||||
|
'indexes': [],
|
||||||
|
},
|
||||||
|
bases=('documents.documentversionpage',),
|
||||||
|
),
|
||||||
|
]
|
||||||
46
mayan/apps/documents/migrations/0055_auto_20191008_2116.py
Normal file
46
mayan/apps/documents/migrations/0055_auto_20191008_2116.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.22 on 2019-10-08 21:16
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
('documents', '0054_auto_20191008_1522'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DocumentPage',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('enabled', models.BooleanField(default=True, verbose_name='Enabled')),
|
||||||
|
('page_number', models.PositiveIntegerField(blank=True, db_index=True, null=True, verbose_name='Page number')),
|
||||||
|
('object_id', models.PositiveIntegerField()),
|
||||||
|
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
|
||||||
|
('document', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pages', to='documents.Document', verbose_name='Document')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Document page',
|
||||||
|
'verbose_name_plural': 'Document pages',
|
||||||
|
'ordering': ('page_number',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='documentversionpage',
|
||||||
|
options={'ordering': ('page_number',), 'verbose_name': 'Document version page', 'verbose_name_plural': 'Document version pages'},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='documentversionpage',
|
||||||
|
name='document_version',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pages', to='documents.DocumentVersion', verbose_name='Document version'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='documentpage',
|
||||||
|
unique_together=set([('document', 'page_number')]),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -2,4 +2,5 @@ from .document_models import * # NOQA
|
|||||||
from .document_page_models import * # NOQA
|
from .document_page_models import * # NOQA
|
||||||
from .document_type_models import * # NOQA
|
from .document_type_models import * # NOQA
|
||||||
from .document_version_models import * # NOQA
|
from .document_version_models import * # NOQA
|
||||||
|
from .document_version_page_models import * # NOQA
|
||||||
from .misc_models import * # NOQA
|
from .misc_models import * # NOQA
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import uuid
|
|||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
from django.db import models
|
from django.db import models, transaction
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
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.timezone import now
|
||||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ from ..events import (
|
|||||||
event_document_create, event_document_properties_edit,
|
event_document_create, event_document_properties_edit,
|
||||||
event_document_type_change,
|
event_document_type_change,
|
||||||
)
|
)
|
||||||
|
from ..literals import DOCUMENT_IMAGES_CACHE_NAME
|
||||||
from ..managers import DocumentManager, PassthroughManager, TrashCanManager
|
from ..managers import DocumentManager, PassthroughManager, TrashCanManager
|
||||||
from ..settings import setting_language
|
from ..settings import setting_language
|
||||||
from ..signals import post_document_type_change
|
from ..signals import post_document_type_change
|
||||||
@@ -102,6 +104,18 @@ class Document(models.Model):
|
|||||||
)
|
)
|
||||||
return RecentDocument.objects.add_document_for_user(user, self)
|
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
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
to_trash = kwargs.pop('to_trash', True)
|
to_trash = kwargs.pop('to_trash', True)
|
||||||
|
|
||||||
@@ -165,6 +179,22 @@ class Document(models.Model):
|
|||||||
"""
|
"""
|
||||||
return self.latest_version.open(*args, **kwargs)
|
return self.latest_version.open(*args, **kwargs)
|
||||||
|
|
||||||
|
def reset_pages(self):
|
||||||
|
with transaction.atomic():
|
||||||
|
for page in self.pages.all():
|
||||||
|
page.delete()
|
||||||
|
|
||||||
|
self.latest_version.update_page_count()
|
||||||
|
|
||||||
|
for version_page in self.latest_version.pages.all():
|
||||||
|
document_page = self.pages.create(
|
||||||
|
#content_type = models.ForeignKey(
|
||||||
|
# on_delete=models.CASCADE, to=ContentType
|
||||||
|
#)
|
||||||
|
#object_id = models.PositiveIntegerField()
|
||||||
|
content_object = version_page
|
||||||
|
)
|
||||||
|
|
||||||
def restore(self):
|
def restore(self):
|
||||||
self.in_trash = False
|
self.in_trash = False
|
||||||
self.save()
|
self.save()
|
||||||
@@ -234,28 +264,31 @@ class Document(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def page_count(self):
|
def page_count(self):
|
||||||
return self.latest_version.page_count
|
return self.pages.count()
|
||||||
|
#return self.latest_version.page_count
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pages_all(self):
|
def pages_all(self):
|
||||||
try:
|
return self.pages.all()
|
||||||
return self.latest_version.pages_all
|
#try:
|
||||||
except AttributeError:
|
# return self.latest_version.pages_all
|
||||||
# Document has no version yet
|
#except AttributeError:
|
||||||
DocumentPage = apps.get_model(
|
# # Document has no version yet
|
||||||
app_label='documents', model_name='DocumentPage'
|
# DocumentPage = apps.get_model(
|
||||||
)
|
# app_label='documents', model_name='DocumentPage'
|
||||||
|
# )
|
||||||
|
|
||||||
return DocumentPage.objects.none()
|
# return DocumentPage.objects.none()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pages(self):
|
def pages(self):
|
||||||
try:
|
return self.pages.all()
|
||||||
return self.latest_version.pages
|
#try:
|
||||||
except AttributeError:
|
# return self.latest_version.pages
|
||||||
# Document has no version yet
|
#except AttributeError:
|
||||||
DocumentPage = apps.get_model(
|
# # Document has no version yet
|
||||||
app_label='documents', model_name='DocumentPage'
|
# DocumentPage = apps.get_model(
|
||||||
)
|
# app_label='documents', model_name='DocumentVersionPage'
|
||||||
|
# )
|
||||||
|
|
||||||
return DocumentPage.objects.none()
|
# return DocumentPage.objects.none()
|
||||||
|
|||||||
@@ -4,14 +4,16 @@ import logging
|
|||||||
|
|
||||||
from furl import furl
|
from furl import furl
|
||||||
|
|
||||||
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import Max
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.encoding import force_text, python_2_unicode_compatible
|
from django.utils.encoding import force_text, python_2_unicode_compatible
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.converter.literals import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION
|
from mayan.apps.converter.literals import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION
|
||||||
|
|
||||||
from mayan.apps.converter.models import LayerTransformation
|
from mayan.apps.converter.models import LayerTransformation
|
||||||
from mayan.apps.converter.transformations import (
|
from mayan.apps.converter.transformations import (
|
||||||
BaseTransformation, TransformationResize, TransformationRotate,
|
BaseTransformation, TransformationResize, TransformationRotate,
|
||||||
@@ -26,25 +28,32 @@ from ..settings import (
|
|||||||
setting_zoom_min_level
|
setting_zoom_min_level
|
||||||
)
|
)
|
||||||
|
|
||||||
from .document_version_models import DocumentVersion
|
from .document_models import Document
|
||||||
|
#from .document_version_page_models import DocumentVersionPage
|
||||||
|
|
||||||
__all__ = ('DocumentPage', 'DocumentPageResult')
|
__all__ = ('DocumentPage',)# 'DocumentPageResult')
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class DocumentPage(models.Model):
|
class DocumentPage(models.Model):
|
||||||
"""
|
"""
|
||||||
Model that describes a document version page
|
Model that describes a document page
|
||||||
"""
|
"""
|
||||||
document_version = models.ForeignKey(
|
document = models.ForeignKey(
|
||||||
on_delete=models.CASCADE, related_name='version_pages', to=DocumentVersion,
|
on_delete=models.CASCADE, related_name='pages', to=Document,
|
||||||
verbose_name=_('Document version')
|
verbose_name=_('Document')
|
||||||
)
|
)
|
||||||
enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
|
enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
|
||||||
page_number = models.PositiveIntegerField(
|
page_number = models.PositiveIntegerField(
|
||||||
db_index=True, default=1, editable=False,
|
db_index=True, blank=True, null=True, verbose_name=_('Page number')
|
||||||
verbose_name=_('Page number')
|
)
|
||||||
|
content_type = models.ForeignKey(
|
||||||
|
on_delete=models.CASCADE, to=ContentType
|
||||||
|
)
|
||||||
|
object_id = models.PositiveIntegerField()
|
||||||
|
content_object = GenericForeignKey(
|
||||||
|
ct_field='content_type', fk_field='object_id'
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = DocumentPageManager()
|
objects = DocumentPageManager()
|
||||||
@@ -52,6 +61,7 @@ class DocumentPage(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('page_number',)
|
ordering = ('page_number',)
|
||||||
|
unique_together = ('document', 'page_number')
|
||||||
verbose_name = _('Document page')
|
verbose_name = _('Document page')
|
||||||
verbose_name_plural = _('Document pages')
|
verbose_name_plural = _('Document pages')
|
||||||
|
|
||||||
@@ -60,7 +70,7 @@ class DocumentPage(models.Model):
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def cache_partition(self):
|
def cache_partition(self):
|
||||||
partition, created = self.document_version.cache.partitions.get_or_create(
|
partition, created = self.document.cache.partitions.get_or_create(
|
||||||
name=self.uuid
|
name=self.uuid
|
||||||
)
|
)
|
||||||
return partition
|
return partition
|
||||||
@@ -69,19 +79,19 @@ class DocumentPage(models.Model):
|
|||||||
self.cache_partition.delete()
|
self.cache_partition.delete()
|
||||||
super(DocumentPage, self).delete(*args, **kwargs)
|
super(DocumentPage, self).delete(*args, **kwargs)
|
||||||
|
|
||||||
def detect_orientation(self):
|
#def detect_orientation(self):
|
||||||
with self.document_version.open() as file_object:
|
# with self.document_version.open() as file_object:
|
||||||
converter = get_converter_class()(
|
# converter = get_converter_class()(
|
||||||
file_object=file_object,
|
# file_object=file_object,
|
||||||
mime_type=self.document_version.mimetype
|
# mime_type=self.document_version.mimetype
|
||||||
)
|
# )
|
||||||
return converter.detect_orientation(
|
# return converter.detect_orientation(
|
||||||
page_number=self.page_number
|
# page_number=self.page_number
|
||||||
)
|
# )
|
||||||
|
|
||||||
@property
|
#@property
|
||||||
def document(self):
|
#def document(self):
|
||||||
return self.document_version.document
|
# return self.document_version.document
|
||||||
|
|
||||||
def generate_image(self, user=None, **kwargs):
|
def generate_image(self, user=None, **kwargs):
|
||||||
transformation_list = self.get_combined_transformation_list(user=user, **kwargs)
|
transformation_list = self.get_combined_transformation_list(user=user, **kwargs)
|
||||||
@@ -90,7 +100,7 @@ class DocumentPage(models.Model):
|
|||||||
# Check is transformed image is available
|
# Check is transformed image is available
|
||||||
logger.debug('transformations cache filename: %s', combined_cache_filename)
|
logger.debug('transformations cache filename: %s', combined_cache_filename)
|
||||||
|
|
||||||
if not setting_disable_transformed_image_cache.value and self.cache_partition.get_file(filename=combined_cache_filename):
|
if self.cache_partition.get_file(filename=combined_cache_filename):
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'transformations cache file "%s" found', combined_cache_filename
|
'transformations cache file "%s" found', combined_cache_filename
|
||||||
)
|
)
|
||||||
@@ -128,7 +138,8 @@ class DocumentPage(models.Model):
|
|||||||
final_url.args = kwargs
|
final_url.args = kwargs
|
||||||
final_url.path = reverse(
|
final_url.path = reverse(
|
||||||
viewname='rest_api:documentpage-image', kwargs={
|
viewname='rest_api:documentpage-image', kwargs={
|
||||||
'pk': self.document.pk, 'version_pk': self.document_version.pk,
|
'pk': self.document.pk,
|
||||||
|
#'version_pk': self.document_version.pk,
|
||||||
'page_pk': self.pk
|
'page_pk': self.pk
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -190,12 +201,12 @@ class DocumentPage(models.Model):
|
|||||||
return transformation_list
|
return transformation_list
|
||||||
|
|
||||||
def get_image(self, transformations=None):
|
def get_image(self, transformations=None):
|
||||||
cache_filename = 'base_image'
|
cache_filename = 'document_page'
|
||||||
logger.debug('Page cache filename: %s', cache_filename)
|
logger.debug('Page cache filename: %s', cache_filename)
|
||||||
|
|
||||||
cache_file = self.cache_partition.get_file(filename=cache_filename)
|
cache_file = self.cache_partition.get_file(filename=cache_filename)
|
||||||
|
|
||||||
if not setting_disable_base_image_cache.value and cache_file:
|
if cache_file:
|
||||||
logger.debug('Page cache file "%s" found', cache_filename)
|
logger.debug('Page cache file "%s" found', cache_filename)
|
||||||
|
|
||||||
with cache_file.open() as file_object:
|
with cache_file.open() as file_object:
|
||||||
@@ -216,7 +227,12 @@ class DocumentPage(models.Model):
|
|||||||
logger.debug('Page cache file "%s" not found', cache_filename)
|
logger.debug('Page cache file "%s" not found', cache_filename)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self.document_version.get_intermediate_file() as file_object:
|
#with self.document_version.get_intermediate_file() as file_object:
|
||||||
|
#Render or get cached document version page
|
||||||
|
self.content_object.get_image()
|
||||||
|
cache_filename = 'base_image'
|
||||||
|
cache_file = self.content_object.cache_partition.get_file(filename=cache_filename)
|
||||||
|
with cache_file.open() as file_object:
|
||||||
converter = get_converter_class()(
|
converter = get_converter_class()(
|
||||||
file_object=file_object
|
file_object=file_object
|
||||||
)
|
)
|
||||||
@@ -241,28 +257,39 @@ class DocumentPage(models.Model):
|
|||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def get_label(self):
|
||||||
|
return _(
|
||||||
|
'Page %(page_number)d out of %(total_pages)d of %(document)s'
|
||||||
|
) % {
|
||||||
|
'document': force_text(self.document),
|
||||||
|
'page_number': self.page_number,
|
||||||
|
'total_pages': self.document.pages_all.count()
|
||||||
|
}
|
||||||
|
get_label.short_description = _('Label')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_in_trash(self):
|
def is_in_trash(self):
|
||||||
return self.document.is_in_trash
|
return self.document.is_in_trash
|
||||||
|
|
||||||
def get_label(self):
|
|
||||||
return _(
|
|
||||||
'Page %(page_num)d out of %(total_pages)d of %(document)s'
|
|
||||||
) % {
|
|
||||||
'document': force_text(self.document),
|
|
||||||
'page_num': self.page_number,
|
|
||||||
'total_pages': self.document_version.pages_all.count()
|
|
||||||
}
|
|
||||||
get_label.short_description = _('Label')
|
|
||||||
|
|
||||||
def natural_key(self):
|
def natural_key(self):
|
||||||
return (self.page_number, self.document_version.natural_key())
|
return (self.page_number, self.document.natural_key())
|
||||||
natural_key.dependencies = ['documents.DocumentVersion']
|
natural_key.dependencies = ['documents.Document']
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.page_number:
|
||||||
|
last_page_number = DocumentPage.objects.filter(
|
||||||
|
document=self.document
|
||||||
|
).aggregate(Max('page_number'))['page_number__max']
|
||||||
|
if last_page_number is not None:
|
||||||
|
self.page_number = last_page_number + 1
|
||||||
|
else:
|
||||||
|
self.page_number = 1
|
||||||
|
super(DocumentPage, self).save(*args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def siblings(self):
|
def siblings(self):
|
||||||
return DocumentPage.objects.filter(
|
return DocumentPage.objects.filter(
|
||||||
document_version=self.document_version
|
document=self.document
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -271,12 +298,12 @@ class DocumentPage(models.Model):
|
|||||||
Make cache UUID a mix of version ID and page ID to avoid using stale
|
Make cache UUID a mix of version ID and page ID to avoid using stale
|
||||||
images
|
images
|
||||||
"""
|
"""
|
||||||
return '{}-{}'.format(self.document_version.uuid, self.pk)
|
return '{}-{}'.format(self.document.uuid, self.pk)
|
||||||
|
|
||||||
|
|
||||||
class DocumentPageResult(DocumentPage):
|
#class DocumentVersionPageResult(DocumentVersionPage):
|
||||||
class Meta:
|
# class Meta:
|
||||||
ordering = ('document_version__document', 'page_number')
|
# ordering = ('document_version__document', 'page_number')
|
||||||
proxy = True
|
# proxy = True
|
||||||
verbose_name = _('Document page')
|
# verbose_name = _('Document version page')
|
||||||
verbose_name_plural = _('Document pages')
|
# verbose_name_plural = _('Document version pages')
|
||||||
|
|||||||
@@ -246,16 +246,16 @@ class DocumentVersion(models.Model):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@property
|
#@property
|
||||||
def pages_all(self):
|
#def pages_all(self):
|
||||||
DocumentPage = apps.get_model(
|
# DocumentPage = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentPage'
|
# app_label='documents', model_name='DocumentVersionPage'
|
||||||
)
|
# )
|
||||||
return DocumentPage.passthrough.filter(document_version=self)
|
# return DocumentPage.passthrough.filter(document_version=self)
|
||||||
|
|
||||||
@property
|
#@property
|
||||||
def pages(self):
|
#def pages(self):
|
||||||
return self.version_pages.all()
|
# return self.pages.all()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def page_count(self):
|
def page_count(self):
|
||||||
@@ -410,7 +410,7 @@ class DocumentVersion(models.Model):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
DocumentPage = apps.get_model(
|
DocumentPage = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentPage'
|
app_label='documents', model_name='DocumentVersionPage'
|
||||||
)
|
)
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
|
|||||||
284
mayan/apps/documents/models/document_version_page_models.py
Normal file
284
mayan/apps/documents/models/document_version_page_models.py
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from furl import furl
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.encoding import force_text, python_2_unicode_compatible
|
||||||
|
from django.utils.functional import cached_property
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.converter.literals import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION
|
||||||
|
|
||||||
|
from mayan.apps.converter.models import LayerTransformation
|
||||||
|
from mayan.apps.converter.transformations import (
|
||||||
|
BaseTransformation, TransformationResize, TransformationRotate,
|
||||||
|
TransformationZoom
|
||||||
|
)
|
||||||
|
from mayan.apps.converter.utils import get_converter_class
|
||||||
|
|
||||||
|
from ..managers import DocumentVersionPageManager
|
||||||
|
from ..settings import (
|
||||||
|
setting_disable_base_image_cache, setting_disable_transformed_image_cache,
|
||||||
|
setting_display_width, setting_display_height, setting_zoom_max_level,
|
||||||
|
setting_zoom_min_level
|
||||||
|
)
|
||||||
|
|
||||||
|
from .document_version_models import DocumentVersion
|
||||||
|
|
||||||
|
__all__ = ('DocumentVersionPage', 'DocumentVersionPageResult')
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
class DocumentVersionPage(models.Model):
|
||||||
|
"""
|
||||||
|
Model that describes a document version page
|
||||||
|
"""
|
||||||
|
document_version = models.ForeignKey(
|
||||||
|
on_delete=models.CASCADE, related_name='pages', to=DocumentVersion,
|
||||||
|
verbose_name=_('Document version')
|
||||||
|
)
|
||||||
|
enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
|
||||||
|
page_number = models.PositiveIntegerField(
|
||||||
|
db_index=True, default=1, editable=False,
|
||||||
|
verbose_name=_('Page number')
|
||||||
|
)
|
||||||
|
|
||||||
|
objects = DocumentVersionPageManager()
|
||||||
|
#passthrough = models.Manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('page_number',)
|
||||||
|
verbose_name = _('Document version page')
|
||||||
|
verbose_name_plural = _('Document version pages')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.get_label()
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def cache_partition(self):
|
||||||
|
partition, created = self.document_version.cache.partitions.get_or_create(
|
||||||
|
name=self.uuid
|
||||||
|
)
|
||||||
|
return partition
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
self.cache_partition.delete()
|
||||||
|
super(DocumentVersionPage, self).delete(*args, **kwargs)
|
||||||
|
|
||||||
|
#def detect_orientation(self):
|
||||||
|
# with self.document_version.open() as file_object:
|
||||||
|
# converter = get_converter_class()(
|
||||||
|
# file_object=file_object,
|
||||||
|
# mime_type=self.document_version.mimetype
|
||||||
|
# )
|
||||||
|
# return converter.detect_orientation(
|
||||||
|
# page_number=self.page_number
|
||||||
|
# )
|
||||||
|
|
||||||
|
@property
|
||||||
|
def document(self):
|
||||||
|
return self.document_version.document
|
||||||
|
|
||||||
|
def generate_image(self, user=None, **kwargs):
|
||||||
|
transformation_list = self.get_combined_transformation_list(user=user, **kwargs)
|
||||||
|
combined_cache_filename = BaseTransformation.combine(transformation_list)
|
||||||
|
|
||||||
|
# Check is transformed image is available
|
||||||
|
logger.debug('transformations cache filename: %s', combined_cache_filename)
|
||||||
|
|
||||||
|
if self.cache_partition.get_file(filename=combined_cache_filename):
|
||||||
|
logger.debug(
|
||||||
|
'transformations cache file "%s" found', combined_cache_filename
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.debug(
|
||||||
|
'transformations cache file "%s" not found', combined_cache_filename
|
||||||
|
)
|
||||||
|
image = self.get_image(transformations=transformation_list)
|
||||||
|
with self.cache_partition.create_file(filename=combined_cache_filename) as file_object:
|
||||||
|
file_object.write(image.getvalue())
|
||||||
|
|
||||||
|
return combined_cache_filename
|
||||||
|
|
||||||
|
#def get_absolute_url(self):
|
||||||
|
# return reverse(
|
||||||
|
# viewname='documents:document_version_page_view', kwargs={
|
||||||
|
# 'pk': self.pk
|
||||||
|
# }
|
||||||
|
# )
|
||||||
|
|
||||||
|
def get_api_image_url(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Create an unique URL combining:
|
||||||
|
- the page's image URL
|
||||||
|
- the interactive argument
|
||||||
|
- a hash from the server side and interactive transformations
|
||||||
|
The purpose of this unique URL is to allow client side caching
|
||||||
|
if document page images.
|
||||||
|
"""
|
||||||
|
transformations_hash = BaseTransformation.combine(
|
||||||
|
self.get_combined_transformation_list(*args, **kwargs)
|
||||||
|
)
|
||||||
|
|
||||||
|
kwargs.pop('transformations', None)
|
||||||
|
|
||||||
|
final_url = furl()
|
||||||
|
final_url.args = kwargs
|
||||||
|
final_url.path = reverse(
|
||||||
|
viewname='rest_api:documentversionpage-image', kwargs={
|
||||||
|
'pk': self.document.pk, 'version_pk': self.document_version.pk,
|
||||||
|
'page_pk': self.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
final_url.args['_hash'] = transformations_hash
|
||||||
|
|
||||||
|
return final_url.tostr()
|
||||||
|
|
||||||
|
def get_combined_transformation_list(self, user=None, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Return a list of transformation containing the server side
|
||||||
|
document page transformation as well as tranformations created
|
||||||
|
from the arguments as transient interactive transformation.
|
||||||
|
"""
|
||||||
|
# Convert arguments into transformations
|
||||||
|
transformations = kwargs.get('transformations', [])
|
||||||
|
|
||||||
|
# Set sensible defaults if the argument is not specified or if the
|
||||||
|
# argument is None
|
||||||
|
width = kwargs.get('width', setting_display_width.value) or setting_display_width.value
|
||||||
|
height = kwargs.get('height', setting_display_height.value) or setting_display_height.value
|
||||||
|
rotation = kwargs.get('rotation', DEFAULT_ROTATION) or DEFAULT_ROTATION
|
||||||
|
zoom_level = kwargs.get('zoom', DEFAULT_ZOOM_LEVEL) or DEFAULT_ZOOM_LEVEL
|
||||||
|
|
||||||
|
if zoom_level < setting_zoom_min_level.value:
|
||||||
|
zoom_level = setting_zoom_min_level.value
|
||||||
|
|
||||||
|
if zoom_level > setting_zoom_max_level.value:
|
||||||
|
zoom_level = setting_zoom_max_level.value
|
||||||
|
|
||||||
|
# Generate transformation hash
|
||||||
|
transformation_list = []
|
||||||
|
|
||||||
|
maximum_layer_order = kwargs.get('maximum_layer_order', None)
|
||||||
|
|
||||||
|
# Stored transformations first
|
||||||
|
for stored_transformation in LayerTransformation.objects.get_for_object(
|
||||||
|
self, maximum_layer_order=maximum_layer_order, as_classes=True,
|
||||||
|
user=user
|
||||||
|
):
|
||||||
|
transformation_list.append(stored_transformation)
|
||||||
|
|
||||||
|
# Interactive transformations second
|
||||||
|
for transformation in transformations:
|
||||||
|
transformation_list.append(transformation)
|
||||||
|
|
||||||
|
if rotation:
|
||||||
|
transformation_list.append(
|
||||||
|
TransformationRotate(degrees=rotation)
|
||||||
|
)
|
||||||
|
|
||||||
|
if width:
|
||||||
|
transformation_list.append(
|
||||||
|
TransformationResize(width=width, height=height)
|
||||||
|
)
|
||||||
|
|
||||||
|
if zoom_level:
|
||||||
|
transformation_list.append(TransformationZoom(percent=zoom_level))
|
||||||
|
|
||||||
|
return transformation_list
|
||||||
|
|
||||||
|
def get_image(self, transformations=None):
|
||||||
|
cache_filename = 'base_image'
|
||||||
|
logger.debug('Page cache filename: %s', cache_filename)
|
||||||
|
|
||||||
|
cache_file = self.cache_partition.get_file(filename=cache_filename)
|
||||||
|
|
||||||
|
if cache_file:
|
||||||
|
logger.debug('Page cache file "%s" found', cache_filename)
|
||||||
|
|
||||||
|
with cache_file.open() as file_object:
|
||||||
|
converter = get_converter_class()(
|
||||||
|
file_object=file_object
|
||||||
|
)
|
||||||
|
|
||||||
|
converter.seek_page(page_number=0)
|
||||||
|
|
||||||
|
# This code is also repeated below to allow using a context
|
||||||
|
# manager with cache_file.open and close it automatically.
|
||||||
|
# Apply runtime transformations
|
||||||
|
for transformation in transformations or []:
|
||||||
|
converter.transform(transformation=transformation)
|
||||||
|
|
||||||
|
return converter.get_page()
|
||||||
|
else:
|
||||||
|
logger.debug('Page cache file "%s" not found', cache_filename)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with self.document_version.get_intermediate_file() as file_object:
|
||||||
|
converter = get_converter_class()(
|
||||||
|
file_object=file_object
|
||||||
|
)
|
||||||
|
converter.seek_page(page_number=self.page_number - 1)
|
||||||
|
|
||||||
|
page_image = converter.get_page()
|
||||||
|
|
||||||
|
# Since open "wb+" doesn't create files, create it explicitly
|
||||||
|
with self.cache_partition.create_file(filename=cache_filename) as file_object:
|
||||||
|
file_object.write(page_image.getvalue())
|
||||||
|
|
||||||
|
# Apply runtime transformations
|
||||||
|
for transformation in transformations or []:
|
||||||
|
converter.transform(transformation=transformation)
|
||||||
|
|
||||||
|
return converter.get_page()
|
||||||
|
except Exception as exception:
|
||||||
|
# Cleanup in case of error
|
||||||
|
logger.error(
|
||||||
|
'Error creating page cache file "%s"; %s',
|
||||||
|
cache_filename, exception
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
#@property
|
||||||
|
#def is_in_trash(self):
|
||||||
|
# return self.document.is_in_trash
|
||||||
|
|
||||||
|
def get_label(self):
|
||||||
|
return _(
|
||||||
|
'Version page %(page_number)d out of %(total_pages)d of %(document)s'
|
||||||
|
) % {
|
||||||
|
'document': force_text(self.document),
|
||||||
|
'page_number': self.page_number,
|
||||||
|
'total_pages': self.document_version.pages.count()
|
||||||
|
}
|
||||||
|
get_label.short_description = _('Label')
|
||||||
|
|
||||||
|
def natural_key(self):
|
||||||
|
return (self.page_number, self.document_version.natural_key())
|
||||||
|
natural_key.dependencies = ['documents.DocumentVersion']
|
||||||
|
|
||||||
|
#@property
|
||||||
|
#def siblings(self):
|
||||||
|
# return DocumentVersionPage.objects.filter(
|
||||||
|
# document_version=self.document_version
|
||||||
|
# )
|
||||||
|
|
||||||
|
@property
|
||||||
|
def uuid(self):
|
||||||
|
"""
|
||||||
|
Make cache UUID a mix of version ID and page ID to avoid using stale
|
||||||
|
images
|
||||||
|
"""
|
||||||
|
return '{}-{}'.format(self.document_version.uuid, self.pk)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentVersionPageResult(DocumentVersionPage):
|
||||||
|
class Meta:
|
||||||
|
ordering = ('document_version__document', 'page_number')
|
||||||
|
proxy = True
|
||||||
|
verbose_name = _('Document version page')
|
||||||
|
verbose_name_plural = _('Document version pages')
|
||||||
@@ -30,6 +30,10 @@ queue_converter.add_task_type(
|
|||||||
dotted_path='mayan.apps.documents.tasks.task_generate_document_page_image',
|
dotted_path='mayan.apps.documents.tasks.task_generate_document_page_image',
|
||||||
label=_('Generate document page image')
|
label=_('Generate document page image')
|
||||||
)
|
)
|
||||||
|
queue_converter.add_task_type(
|
||||||
|
dotted_path='mayan.apps.documents.tasks.task_generate_document_version_page_image',
|
||||||
|
label=_('Generate document version page image')
|
||||||
|
)
|
||||||
|
|
||||||
queue_documents.add_task_type(
|
queue_documents.add_task_type(
|
||||||
dotted_path='mayan.apps.documents.tasks.task_delete_document',
|
dotted_path='mayan.apps.documents.tasks.task_delete_document',
|
||||||
@@ -66,6 +70,10 @@ queue_tools.add_task_type(
|
|||||||
label=_('Duplicated document scan')
|
label=_('Duplicated document scan')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
queue_uploads.add_task_type(
|
||||||
|
dotted_path='mayan.apps.documents.tasks.task_document_reset_pages',
|
||||||
|
label=_('Reset document pages')
|
||||||
|
)
|
||||||
queue_uploads.add_task_type(
|
queue_uploads.add_task_type(
|
||||||
dotted_path='mayan.apps.documents.tasks.task_update_page_count',
|
dotted_path='mayan.apps.documents.tasks.task_update_page_count',
|
||||||
label=_('Update document page count')
|
label=_('Update document page count')
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ def transformation_format_uuid(term_string):
|
|||||||
def get_queryset_page_search_queryset():
|
def get_queryset_page_search_queryset():
|
||||||
# Ignore documents in trash can
|
# Ignore documents in trash can
|
||||||
DocumentPage = apps.get_model(
|
DocumentPage = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentPage'
|
app_label='documents', model_name='DocumentVersionPage'
|
||||||
)
|
)
|
||||||
return DocumentPage.objects.filter(document_version__document__in_trash=False)
|
return DocumentPage.objects.filter(document_version__document__in_trash=False)
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ document_search.add_model_field(
|
|||||||
|
|
||||||
document_page_search = SearchModel(
|
document_page_search = SearchModel(
|
||||||
app_label='documents', list_mode=LIST_MODE_CHOICE_ITEM,
|
app_label='documents', list_mode=LIST_MODE_CHOICE_ITEM,
|
||||||
model_name='DocumentPage', permission=permission_document_view,
|
model_name='DocumentVersionPage', permission=permission_document_view,
|
||||||
queryset=get_queryset_page_search_queryset,
|
queryset=get_queryset_page_search_queryset,
|
||||||
serializer_path='mayan.apps.documents.serializers.DocumentPageSerializer'
|
serializer_path='mayan.apps.documents.serializers.DocumentPageSerializer'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,42 +8,40 @@ from rest_framework.reverse import reverse
|
|||||||
from mayan.apps.common.models import SharedUploadedFile
|
from mayan.apps.common.models import SharedUploadedFile
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Document, DocumentVersion, DocumentPage, DocumentType,
|
Document, DocumentPage, DocumentType, DocumentTypeFilename,
|
||||||
DocumentTypeFilename, RecentDocument
|
DocumentVersion, DocumentVersionPage, RecentDocument
|
||||||
)
|
)
|
||||||
from .settings import setting_language
|
from .settings import setting_language
|
||||||
from .tasks import task_upload_new_version
|
from .tasks import task_upload_new_version
|
||||||
|
|
||||||
|
|
||||||
class DocumentPageSerializer(serializers.HyperlinkedModelSerializer):
|
class DocumentPageSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
document_version_url = serializers.SerializerMethodField()
|
document_url = serializers.SerializerMethodField()
|
||||||
image_url = serializers.SerializerMethodField()
|
image_url = serializers.SerializerMethodField()
|
||||||
url = serializers.SerializerMethodField()
|
url = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = ('document_version_url', 'image_url', 'page_number', 'url')
|
fields = ('document_url', 'image_url', 'page_number', 'url')
|
||||||
model = DocumentPage
|
model = DocumentPage
|
||||||
|
|
||||||
def get_document_version_url(self, instance):
|
def get_document_url(self, instance):
|
||||||
return reverse(
|
return reverse(
|
||||||
viewname='rest_api:documentversion-detail', args=(
|
viewname='rest_api:document-detail', args=(
|
||||||
instance.document.pk, instance.document_version.pk,
|
instance.document.pk,
|
||||||
), request=self.context['request'], format=self.context['format']
|
), request=self.context['request'], format=self.context['format']
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_image_url(self, instance):
|
def get_image_url(self, instance):
|
||||||
return reverse(
|
return reverse(
|
||||||
viewname='rest_api:documentpage-image', args=(
|
viewname='rest_api:documentpage-image', args=(
|
||||||
instance.document.pk, instance.document_version.pk,
|
instance.document.pk, instance.pk,
|
||||||
instance.pk,
|
|
||||||
), request=self.context['request'], format=self.context['format']
|
), request=self.context['request'], format=self.context['format']
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_url(self, instance):
|
def get_url(self, instance):
|
||||||
return reverse(
|
return reverse(
|
||||||
viewname='rest_api:documentpage-detail', args=(
|
viewname='rest_api:documentpage-detail', args=(
|
||||||
instance.document.pk, instance.document_version.pk,
|
instance.document.pk, instance.pk,
|
||||||
instance.pk,
|
|
||||||
), request=self.context['request'], format=self.context['format']
|
), request=self.context['request'], format=self.context['format']
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -97,6 +95,39 @@ class WritableDocumentTypeSerializer(serializers.ModelSerializer):
|
|||||||
return obj.documents.count()
|
return obj.documents.count()
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentVersionPageSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
document_version_url = serializers.SerializerMethodField()
|
||||||
|
image_url = serializers.SerializerMethodField()
|
||||||
|
url = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = ('document_version_url', 'image_url', 'page_number', 'url')
|
||||||
|
model = DocumentVersionPage
|
||||||
|
|
||||||
|
def get_document_version_url(self, instance):
|
||||||
|
return reverse(
|
||||||
|
viewname='rest_api:documentversion-detail', args=(
|
||||||
|
instance.document.pk, instance.document_version.pk,
|
||||||
|
), request=self.context['request'], format=self.context['format']
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_image_url(self, instance):
|
||||||
|
return reverse(
|
||||||
|
viewname='rest_api:documentversionpage-image', args=(
|
||||||
|
instance.document.pk, instance.document_version.pk,
|
||||||
|
instance.pk,
|
||||||
|
), request=self.context['request'], format=self.context['format']
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_url(self, instance):
|
||||||
|
return reverse(
|
||||||
|
viewname='rest_api:documentversionpage-detail', args=(
|
||||||
|
instance.document.pk, instance.document_version.pk,
|
||||||
|
instance.pk,
|
||||||
|
), request=self.context['request'], format=self.context['format']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DocumentVersionSerializer(serializers.HyperlinkedModelSerializer):
|
class DocumentVersionSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
document_url = serializers.SerializerMethodField()
|
document_url = serializers.SerializerMethodField()
|
||||||
download_url = serializers.SerializerMethodField()
|
download_url = serializers.SerializerMethodField()
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ def new_documents_per_month():
|
|||||||
|
|
||||||
def new_document_pages_per_month():
|
def new_document_pages_per_month():
|
||||||
DocumentPage = apps.get_model(
|
DocumentPage = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentPage'
|
app_label='documents', model_name='DocumentVersionPage'
|
||||||
)
|
)
|
||||||
|
|
||||||
qss = qsstats.QuerySetStats(
|
qss = qsstats.QuerySetStats(
|
||||||
@@ -106,7 +106,7 @@ def new_document_pages_this_month(user=None):
|
|||||||
app_label='acls', model_name='AccessControlList'
|
app_label='acls', model_name='AccessControlList'
|
||||||
)
|
)
|
||||||
DocumentPage = apps.get_model(
|
DocumentPage = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentPage'
|
app_label='documents', model_name='DocumentVersionPage'
|
||||||
)
|
)
|
||||||
|
|
||||||
queryset = DocumentPage.objects.all()
|
queryset = DocumentPage.objects.all()
|
||||||
@@ -195,7 +195,7 @@ def total_document_version_per_month():
|
|||||||
|
|
||||||
def total_document_page_per_month():
|
def total_document_page_per_month():
|
||||||
DocumentPage = apps.get_model(
|
DocumentPage = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentPage'
|
app_label='documents', model_name='DocumentVersionPage'
|
||||||
)
|
)
|
||||||
|
|
||||||
qss = qsstats.QuerySetStats(
|
qss = qsstats.QuerySetStats(
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ from django.db import OperationalError
|
|||||||
from mayan.celery import app
|
from mayan.celery import app
|
||||||
|
|
||||||
from .literals import (
|
from .literals import (
|
||||||
UPDATE_PAGE_COUNT_RETRY_DELAY, UPLOAD_NEW_VERSION_RETRY_DELAY
|
RETRY_DELAY_DOCUMENT_RESET_PAGES, UPDATE_PAGE_COUNT_RETRY_DELAY,
|
||||||
|
UPLOAD_NEW_VERSION_RETRY_DELAY
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -64,6 +65,25 @@ def task_delete_stubs():
|
|||||||
logger.info(msg='Finshed')
|
logger.info(msg='Finshed')
|
||||||
|
|
||||||
|
|
||||||
|
@app.task(bind=True, default_retry_delay=RETRY_DELAY_DOCUMENT_RESET_PAGES, ignore_result=True)
|
||||||
|
def task_document_reset_pages(self, document_id):
|
||||||
|
Document = apps.get_model(
|
||||||
|
app_label='documents', model_name='Document'
|
||||||
|
)
|
||||||
|
|
||||||
|
document = Document.objects.get(pk=document_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
document.reset_pages()
|
||||||
|
except OperationalError as exception:
|
||||||
|
logger.warning(
|
||||||
|
'Operational error during attempt to reset pages for '
|
||||||
|
'document: %s; %s. Retrying.', document,
|
||||||
|
exception
|
||||||
|
)
|
||||||
|
raise self.retry(exc=exception)
|
||||||
|
|
||||||
|
|
||||||
@app.task()
|
@app.task()
|
||||||
def task_generate_document_page_image(document_page_id, user_id=None, **kwargs):
|
def task_generate_document_page_image(document_page_id, user_id=None, **kwargs):
|
||||||
DocumentPage = apps.get_model(
|
DocumentPage = apps.get_model(
|
||||||
@@ -80,6 +100,22 @@ def task_generate_document_page_image(document_page_id, user_id=None, **kwargs):
|
|||||||
return document_page.generate_image(user=user, **kwargs)
|
return document_page.generate_image(user=user, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@app.task()
|
||||||
|
def task_generate_document_version_page_image(document_version_page_id, user_id=None, **kwargs):
|
||||||
|
DocumentVersionPage = apps.get_model(
|
||||||
|
app_label='documents', model_name='DocumentVersionPage'
|
||||||
|
)
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
if user_id:
|
||||||
|
user = User.objects.get(pk=user_id)
|
||||||
|
else:
|
||||||
|
user = None
|
||||||
|
|
||||||
|
document_version_page = DocumentVersionPage.objects.get(pk=document_version_page_id)
|
||||||
|
return document_version_page.generate_image(user=user, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@app.task(ignore_result=True)
|
@app.task(ignore_result=True)
|
||||||
def task_scan_duplicates_all():
|
def task_scan_duplicates_all():
|
||||||
DuplicatedDocument = apps.get_model(
|
DuplicatedDocument = apps.get_model(
|
||||||
|
|||||||
@@ -4,13 +4,15 @@ from django.conf.urls import url
|
|||||||
|
|
||||||
from .api_views import (
|
from .api_views import (
|
||||||
APITrashedDocumentListView, APIDeletedDocumentRestoreView,
|
APITrashedDocumentListView, APIDeletedDocumentRestoreView,
|
||||||
APIDeletedDocumentView, APIDocumentDownloadView, APIDocumentView,
|
APIDeletedDocumentView, APIDocumentDownloadView, APIDocumentPageListView,
|
||||||
APIDocumentListView, APIDocumentVersionDownloadView,
|
APIDocumentView, APIDocumentListView, APIDocumentVersionDownloadView,
|
||||||
APIDocumentPageImageView, APIDocumentPageView,
|
APIDocumentPageImageView, APIDocumentPageView,
|
||||||
APIDocumentTypeDocumentListView, APIDocumentTypeListView,
|
APIDocumentTypeDocumentListView, APIDocumentTypeListView,
|
||||||
APIDocumentTypeView, APIDocumentVersionsListView,
|
APIDocumentTypeView, APIDocumentVersionsListView,
|
||||||
APIDocumentVersionPageListView, APIDocumentVersionView,
|
APIDocumentVersionPageListView, APIDocumentVersionView,
|
||||||
APIRecentDocumentListView
|
APIRecentDocumentListView,
|
||||||
|
APIDocumentVersionPageView,
|
||||||
|
APIDocumentVersionPageImageView
|
||||||
)
|
)
|
||||||
from .views.document_views import (
|
from .views.document_views import (
|
||||||
DocumentDocumentTypeEditView, DocumentDownloadFormView,
|
DocumentDocumentTypeEditView, DocumentDownloadFormView,
|
||||||
@@ -405,6 +407,11 @@ api_urls = [
|
|||||||
view=APIDocumentVersionPageListView.as_view(),
|
view=APIDocumentVersionPageListView.as_view(),
|
||||||
name='documentversion-page-list'
|
name='documentversion-page-list'
|
||||||
),
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^documents/(?P<pk>[0-9]+)/pages/$',
|
||||||
|
view=APIDocumentPageListView.as_view(),
|
||||||
|
name='document-page-list'
|
||||||
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^documents/(?P<pk>[0-9]+)/versions/(?P<version_pk>[0-9]+)/download/$',
|
regex=r'^documents/(?P<pk>[0-9]+)/versions/(?P<version_pk>[0-9]+)/download/$',
|
||||||
view=APIDocumentVersionDownloadView.as_view(),
|
view=APIDocumentVersionDownloadView.as_view(),
|
||||||
@@ -416,12 +423,20 @@ api_urls = [
|
|||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^documents/(?P<pk>[0-9]+)/versions/(?P<version_pk>[0-9]+)/pages/(?P<page_pk>[0-9]+)$',
|
regex=r'^documents/(?P<pk>[0-9]+)/versions/(?P<version_pk>[0-9]+)/pages/(?P<page_pk>[0-9]+)$',
|
||||||
|
view=APIDocumentVersionPageView.as_view(), name='documentversionpage-detail'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^documents/(?P<pk>[0-9]+)/pages/(?P<page_pk>[0-9]+)$',
|
||||||
view=APIDocumentPageView.as_view(), name='documentpage-detail'
|
view=APIDocumentPageView.as_view(), name='documentpage-detail'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^documents/(?P<pk>[0-9]+)/versions/(?P<version_pk>[0-9]+)/pages/(?P<page_pk>[0-9]+)/image/$',
|
regex=r'^documents/(?P<pk>[0-9]+)/pages/(?P<page_pk>[0-9]+)/image/$',
|
||||||
view=APIDocumentPageImageView.as_view(), name='documentpage-image'
|
view=APIDocumentPageImageView.as_view(), name='documentpage-image'
|
||||||
),
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^documents/(?P<pk>[0-9]+)/versions/(?P<version_pk>[0-9]+)/pages/(?P<page_pk>[0-9]+)/image/$',
|
||||||
|
view=APIDocumentVersionPageImageView.as_view(), name='documentversionpage-image'
|
||||||
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^trashed_documents/$',
|
regex=r'^trashed_documents/$',
|
||||||
view=APITrashedDocumentListView.as_view(), name='trasheddocument-list'
|
view=APITrashedDocumentListView.as_view(), name='trasheddocument-list'
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from mayan.apps.converter.literals import DEFAULT_ROTATION, DEFAULT_ZOOM_LEVEL
|
|||||||
from ..forms import DocumentPageForm
|
from ..forms import DocumentPageForm
|
||||||
from ..icons import icon_document_pages
|
from ..icons import icon_document_pages
|
||||||
from ..links import link_document_update_page_count
|
from ..links import link_document_update_page_count
|
||||||
from ..models import Document, DocumentPage
|
from ..models import Document, DocumentVersionPage
|
||||||
from ..permissions import permission_document_edit, permission_document_view
|
from ..permissions import permission_document_edit, permission_document_view
|
||||||
from ..settings import (
|
from ..settings import (
|
||||||
setting_rotation_step, setting_zoom_percent_step, setting_zoom_max_level,
|
setting_rotation_step, setting_zoom_percent_step, setting_zoom_max_level,
|
||||||
@@ -69,7 +69,7 @@ class DocumentPageListView(ExternalObjectMixin, SingleObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
class DocumentPageNavigationBase(ExternalObjectMixin, RedirectView):
|
class DocumentPageNavigationBase(ExternalObjectMixin, RedirectView):
|
||||||
external_object_class = DocumentPage
|
external_object_class = DocumentVersionPage
|
||||||
external_object_permission = permission_document_view
|
external_object_permission = permission_document_view
|
||||||
external_object_pk_url_kwarg = 'pk'
|
external_object_pk_url_kwarg = 'pk'
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ class DocumentPageNavigationPrevious(DocumentPageNavigationBase):
|
|||||||
|
|
||||||
|
|
||||||
class DocumentPageView(ExternalObjectMixin, SimpleView):
|
class DocumentPageView(ExternalObjectMixin, SimpleView):
|
||||||
external_object_class = DocumentPage
|
external_object_class = DocumentVersionPage
|
||||||
external_object_permission = permission_document_view
|
external_object_permission = permission_document_view
|
||||||
external_object_pk_url_kwarg = 'pk'
|
external_object_pk_url_kwarg = 'pk'
|
||||||
template_name = 'appearance/generic_form.html'
|
template_name = 'appearance/generic_form.html'
|
||||||
@@ -204,7 +204,7 @@ class DocumentPageViewResetView(RedirectView):
|
|||||||
|
|
||||||
|
|
||||||
class DocumentPageInteractiveTransformation(ExternalObjectMixin, RedirectView):
|
class DocumentPageInteractiveTransformation(ExternalObjectMixin, RedirectView):
|
||||||
external_object_class = DocumentPage
|
external_object_class = DocumentVersionPage
|
||||||
external_object_permission = permission_document_view
|
external_object_permission = permission_document_view
|
||||||
external_object_pk_url_kwarg = 'pk'
|
external_object_pk_url_kwarg = 'pk'
|
||||||
|
|
||||||
@@ -289,7 +289,7 @@ class DocumentPageDisable(MultipleObjectConfirmActionView):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def get_source_queryset(self):
|
def get_source_queryset(self):
|
||||||
return DocumentPage.passthrough.all()
|
return DocumentVersionPage.passthrough.all()
|
||||||
|
|
||||||
def object_action(self, form, instance):
|
def object_action(self, form, instance):
|
||||||
instance.enabled = False
|
instance.enabled = False
|
||||||
@@ -319,7 +319,7 @@ class DocumentPageEnable(MultipleObjectConfirmActionView):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def get_source_queryset(self):
|
def get_source_queryset(self):
|
||||||
return DocumentPage.passthrough.all()
|
return DocumentVersionPage.passthrough.all()
|
||||||
|
|
||||||
def object_action(self, form, instance):
|
def object_action(self, form, instance):
|
||||||
instance.enabled = True
|
instance.enabled = True
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.22 on 2019-10-08 15:10
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('file_caching', '0002_auto_20190729_0236'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cache',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(db_index=True, help_text='Internal name of the cache.', max_length=128, unique=True, verbose_name='Name'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -76,7 +76,7 @@ class MetadataApp(MayanAppConfig):
|
|||||||
app_label='documents', model_name='Document'
|
app_label='documents', model_name='Document'
|
||||||
)
|
)
|
||||||
DocumentPageResult = apps.get_model(
|
DocumentPageResult = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentPageResult'
|
app_label='documents', model_name='DocumentVersionPageResult'
|
||||||
)
|
)
|
||||||
|
|
||||||
DocumentType = apps.get_model(
|
DocumentType = apps.get_model(
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class OCRApp(MayanAppConfig):
|
|||||||
app_label='documents', model_name='Document'
|
app_label='documents', model_name='Document'
|
||||||
)
|
)
|
||||||
DocumentPage = apps.get_model(
|
DocumentPage = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentPage'
|
app_label='documents', model_name='DocumentVersionPage'
|
||||||
)
|
)
|
||||||
DocumentType = apps.get_model(
|
DocumentType = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentType'
|
app_label='documents', model_name='DocumentType'
|
||||||
@@ -96,9 +96,9 @@ class OCRApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
ModelField(
|
#ModelField(
|
||||||
model=Document, name='versions__version_pages__ocr_content__content'
|
# model=Document, name='versions__pages__ocr_content__content'
|
||||||
)
|
#)
|
||||||
|
|
||||||
ModelPermission.register(
|
ModelPermission.register(
|
||||||
model=Document, permissions=(
|
model=Document, permissions=(
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ from django.db import models
|
|||||||
from django.utils.encoding import force_text, python_2_unicode_compatible
|
from django.utils.encoding import force_text, python_2_unicode_compatible
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.documents.models import DocumentPage, DocumentType, DocumentVersion
|
from mayan.apps.documents.models import (
|
||||||
|
DocumentPage, DocumentType, DocumentVersion
|
||||||
|
)
|
||||||
|
|
||||||
from .managers import (
|
from .managers import (
|
||||||
DocumentPageOCRContentManager, DocumentTypeSettingsManager
|
DocumentPageOCRContentManager, DocumentTypeSettingsManager
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ def task_do_ocr(self, document_version_pk):
|
|||||||
DocumentVersion = apps.get_model(
|
DocumentVersion = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentVersion'
|
app_label='documents', model_name='DocumentVersion'
|
||||||
)
|
)
|
||||||
DocumentPageOCRContent = apps.get_model(
|
DocumentVersionPageOCRContent = apps.get_model(
|
||||||
app_label='ocr', model_name='DocumentPageOCRContent'
|
app_label='ocr', model_name='DocumentVersionPageOCRContent'
|
||||||
)
|
)
|
||||||
|
|
||||||
lock_id = 'task_do_ocr_doc_version-%d' % document_version_pk
|
lock_id = 'task_do_ocr_doc_version-%d' % document_version_pk
|
||||||
@@ -39,7 +39,7 @@ def task_do_ocr(self, document_version_pk):
|
|||||||
'Starting document OCR for document version: %s',
|
'Starting document OCR for document version: %s',
|
||||||
document_version
|
document_version
|
||||||
)
|
)
|
||||||
DocumentPageOCRContent.objects.process_document_version(
|
DocumentVersionPageOCRContent.objects.process_document_version(
|
||||||
document_version=document_version
|
document_version=document_version
|
||||||
)
|
)
|
||||||
except OperationalError as exception:
|
except OperationalError as exception:
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class OCRAPITestCase(DocumentTestMixin, BaseAPITestCase):
|
|||||||
hasattr(self.test_document.pages.first(), 'ocr_content')
|
hasattr(self.test_document.pages.first(), 'ocr_content')
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_document_page_content_view(self):
|
def _request_document_version_page_content_view(self):
|
||||||
return self.get(
|
return self.get(
|
||||||
viewname='rest_api:document-page-ocr-content-view', kwargs={
|
viewname='rest_api:document-page-ocr-content-view', kwargs={
|
||||||
'document_pk': self.test_document.pk,
|
'document_pk': self.test_document.pk,
|
||||||
@@ -75,7 +75,7 @@ class OCRAPITestCase(DocumentTestMixin, BaseAPITestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_get_document_version_page_content_no_access(self):
|
def test_get_document_version_page_content_no_access(self):
|
||||||
response = self._request_document_page_content_view()
|
response = self._request_document_version_page_content_view()
|
||||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
def test_get_document_version_page_content_with_access(self):
|
def test_get_document_version_page_content_with_access(self):
|
||||||
@@ -83,7 +83,7 @@ class OCRAPITestCase(DocumentTestMixin, BaseAPITestCase):
|
|||||||
self.grant_access(
|
self.grant_access(
|
||||||
permission=permission_ocr_content_view, obj=self.test_document
|
permission=permission_ocr_content_view, obj=self.test_document
|
||||||
)
|
)
|
||||||
response = self._request_document_page_content_view()
|
response = self._request_document_version_page_content_view()
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
TEST_DOCUMENT_CONTENT in response.data['content']
|
TEST_DOCUMENT_CONTENT in response.data['content']
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ from ..events import (
|
|||||||
event_ocr_document_content_deleted, event_ocr_document_version_submit,
|
event_ocr_document_content_deleted, event_ocr_document_version_submit,
|
||||||
event_ocr_document_version_finish
|
event_ocr_document_version_finish
|
||||||
)
|
)
|
||||||
from ..models import DocumentPageOCRContent
|
from ..models import DocumentVersionPageOCRContent
|
||||||
|
|
||||||
|
|
||||||
class OCREventsTestCase(GenericDocumentTestCase):
|
class OCREventsTestCase(GenericDocumentTestCase):
|
||||||
def test_document_content_deleted_event(self):
|
def test_document_content_deleted_event(self):
|
||||||
Action.objects.all().delete()
|
Action.objects.all().delete()
|
||||||
DocumentPageOCRContent.objects.delete_content_for(
|
DocumentVersionPageOCRContent.objects.delete_content_for(
|
||||||
document=self.test_document
|
document=self.test_document
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from mayan.apps.documents.tests.base import GenericDocumentViewTestCase
|
from mayan.apps.documents.tests.base import GenericDocumentViewTestCase
|
||||||
|
|
||||||
from ..models import DocumentPageOCRContent
|
from ..models import DocumentVersionPageOCRContent
|
||||||
from ..permissions import (
|
from ..permissions import (
|
||||||
permission_ocr_content_view, permission_ocr_document,
|
permission_ocr_content_view, permission_ocr_document,
|
||||||
permission_document_type_ocr_setup
|
permission_document_type_ocr_setup
|
||||||
@@ -27,9 +27,9 @@ class OCRViewTestMixin(object):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_document_page_content_view(self):
|
def _request_document_version_page_content_view(self):
|
||||||
return self.get(
|
return self.get(
|
||||||
viewname='ocr:document_page_ocr_content', kwargs={
|
viewname='ocr:document_version_page_ocr_content', kwargs={
|
||||||
'pk': self.test_document.pages.first().pk
|
'pk': self.test_document.pages.first().pk
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -86,8 +86,8 @@ class OCRViewsTestCase(OCRViewTestMixin, GenericDocumentViewTestCase):
|
|||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
DocumentPageOCRContent.objects.filter(
|
DocumentVersionPageOCRContent.objects.filter(
|
||||||
document_page=self.test_document.pages.first()
|
document_version_page=self.test_document.pages.first()
|
||||||
).exists()
|
).exists()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -101,28 +101,11 @@ class OCRViewsTestCase(OCRViewTestMixin, GenericDocumentViewTestCase):
|
|||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
DocumentPageOCRContent.objects.filter(
|
DocumentVersionPageOCRContent.objects.filter(
|
||||||
document_page=self.test_document.pages.first()
|
document_version_page=self.test_document.pages.first()
|
||||||
).exists()
|
).exists()
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_document_page_content_view_no_permissions(self):
|
|
||||||
self.test_document.submit_for_ocr()
|
|
||||||
|
|
||||||
response = self._request_document_page_content_view()
|
|
||||||
self.assertEqual(response.status_code, 404)
|
|
||||||
|
|
||||||
def test_document_page_content_view_with_access(self):
|
|
||||||
self.test_document.submit_for_ocr()
|
|
||||||
self.grant_access(
|
|
||||||
obj=self.test_document, permission=permission_ocr_content_view
|
|
||||||
)
|
|
||||||
|
|
||||||
response = self._request_document_page_content_view()
|
|
||||||
self.assertContains(
|
|
||||||
response=response, text=TEST_DOCUMENT_CONTENT, status_code=200
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_document_submit_view_no_permission(self):
|
def test_document_submit_view_no_permission(self):
|
||||||
response = self._request_document_submit_view()
|
response = self._request_document_submit_view()
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
@@ -188,6 +171,23 @@ class OCRViewsTestCase(OCRViewTestMixin, GenericDocumentViewTestCase):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_document_version_page_content_view_no_permissions(self):
|
||||||
|
self.test_document.submit_for_ocr()
|
||||||
|
|
||||||
|
response = self._request_document_version_page_content_view()
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
def test_document_version_page_content_view_with_access(self):
|
||||||
|
self.test_document.submit_for_ocr()
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document, permission=permission_ocr_content_view
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_document_version_page_content_view()
|
||||||
|
self.assertContains(
|
||||||
|
response=response, text=TEST_DOCUMENT_CONTENT, status_code=200
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DocumentTypeOCRViewTestMixin(object):
|
class DocumentTypeOCRViewTestMixin(object):
|
||||||
def _request_document_type_ocr_settings_view(self):
|
def _request_document_type_ocr_settings_view(self):
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ from mayan.apps.common.generics import (
|
|||||||
)
|
)
|
||||||
from mayan.apps.common.mixins import ExternalObjectMixin
|
from mayan.apps.common.mixins import ExternalObjectMixin
|
||||||
from mayan.apps.documents.forms import DocumentTypeFilteredSelectForm
|
from mayan.apps.documents.forms import DocumentTypeFilteredSelectForm
|
||||||
from mayan.apps.documents.models import Document, DocumentPage, DocumentType
|
from mayan.apps.documents.models import (
|
||||||
|
Document, DocumentType, DocumentVersionPage
|
||||||
|
)
|
||||||
|
|
||||||
from .forms import DocumentPageOCRContentForm, DocumentOCRContentForm
|
from .forms import DocumentPageOCRContentForm, DocumentOCRContentForm
|
||||||
from .models import DocumentPageOCRContent, DocumentVersionOCRError
|
from .models import DocumentPageOCRContent, DocumentVersionOCRError
|
||||||
@@ -74,7 +76,7 @@ class DocumentOCRContentView(SingleObjectDetailView):
|
|||||||
|
|
||||||
class DocumentPageOCRContentView(SingleObjectDetailView):
|
class DocumentPageOCRContentView(SingleObjectDetailView):
|
||||||
form_class = DocumentPageOCRContentForm
|
form_class = DocumentPageOCRContentForm
|
||||||
model = DocumentPage
|
model = DocumentVersionPage
|
||||||
object_permission = permission_ocr_content_view
|
object_permission = permission_ocr_content_view
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class RedactionsApp(MayanAppConfig):
|
|||||||
super(RedactionsApp, self).ready()
|
super(RedactionsApp, self).ready()
|
||||||
|
|
||||||
DocumentPage = apps.get_model(
|
DocumentPage = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentPage'
|
app_label='documents', model_name='DocumentVersionPage'
|
||||||
)
|
)
|
||||||
|
|
||||||
link_redaction_list = link_transformation_list.copy(
|
link_redaction_list = link_transformation_list.copy(
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class TagsApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
|
|
||||||
DocumentPageResult = apps.get_model(
|
DocumentPageResult = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentPageResult'
|
app_label='documents', model_name='DocumentVersionPageResult'
|
||||||
)
|
)
|
||||||
|
|
||||||
DocumentTag = self.get_model(model_name='DocumentTag')
|
DocumentTag = self.get_model(model_name='DocumentTag')
|
||||||
|
|||||||
Reference in New Issue
Block a user