Compare commits
26 Commits
master
...
features/d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b7f133249 | ||
|
|
acd8fd2a3e | ||
|
|
beb3b936a6 | ||
|
|
f10cc89847 | ||
|
|
01e79b1089 | ||
|
|
5ea286d4bd | ||
|
|
d865c60091 | ||
|
|
4afe81f306 | ||
|
|
126dcfd609 | ||
|
|
77e3847025 | ||
|
|
4f93beae74 | ||
|
|
af1eae8c52 | ||
|
|
739d496799 | ||
|
|
ff03ea07ca | ||
|
|
03379ab8ec | ||
|
|
a4a12b0cfe | ||
|
|
cf697d3ea7 | ||
|
|
a9077cb47a | ||
|
|
f163dc78d4 | ||
|
|
64abf66f22 | ||
|
|
7fbb94a8ae | ||
|
|
d0ee8aba16 | ||
|
|
5b37c7715d | ||
|
|
8cf807899a | ||
|
|
653f55f84a | ||
|
|
9cf1d44ee7 |
@@ -78,6 +78,13 @@
|
|||||||
Support Docker networks and make it the default.
|
Support Docker networks and make it the default.
|
||||||
Delete the containers to allow the script to be idempotent.
|
Delete the containers to allow the script to be idempotent.
|
||||||
Deploy a Redis container.
|
Deploy a Redis container.
|
||||||
|
- Improve document version upload form.
|
||||||
|
- Use dropzone for document version upload form.
|
||||||
|
- Remove the DOCUMENTS_DISABLE_BASE_IMAGE_CACHE,
|
||||||
|
DOCUMENTS_DISABLE_TRANSFORMED_IMAGE_CACHE, and
|
||||||
|
DOCUMENTS_FIX_ORIENTATION settings.
|
||||||
|
- Support simple search disable via the new
|
||||||
|
SEARCH_DISABLE_SIMPLE_SEARCH setting.
|
||||||
|
|
||||||
3.2.8 (2019-10-01)
|
3.2.8 (2019-10-01)
|
||||||
==================
|
==================
|
||||||
|
|||||||
@@ -10,12 +10,14 @@ from mayan.apps.common.menus import (
|
|||||||
menu_facet, menu_list_facet, menu_main, menu_multi_item, menu_object,
|
menu_facet, menu_list_facet, menu_main, menu_multi_item, menu_object,
|
||||||
menu_secondary
|
menu_secondary
|
||||||
)
|
)
|
||||||
|
from mayan.apps.documents.search import (
|
||||||
|
document_page_search, document_search, document_version_page_search
|
||||||
|
)
|
||||||
from mayan.apps.events.classes import ModelEventType
|
from mayan.apps.events.classes import ModelEventType
|
||||||
from mayan.apps.events.links import (
|
from mayan.apps.events.links import (
|
||||||
link_events_for_object, link_object_event_types_user_subcriptions_list,
|
link_events_for_object, link_object_event_types_user_subcriptions_list,
|
||||||
)
|
)
|
||||||
from mayan.apps.events.permissions import permission_events_view
|
from mayan.apps.events.permissions import permission_events_view
|
||||||
from mayan.apps.documents.search import document_page_search, document_search
|
|
||||||
from mayan.apps.navigation.classes import SourceColumn
|
from mayan.apps.navigation.classes import SourceColumn
|
||||||
|
|
||||||
from .dependencies import * # NOQA
|
from .dependencies import * # NOQA
|
||||||
@@ -115,12 +117,16 @@ class CabinetsApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
|
|
||||||
document_page_search.add_model_field(
|
document_page_search.add_model_field(
|
||||||
field='document_version__document__cabinets__label',
|
field='document__cabinets__label',
|
||||||
label=_('Cabinets')
|
label=_('Cabinets')
|
||||||
)
|
)
|
||||||
document_search.add_model_field(
|
document_search.add_model_field(
|
||||||
field='cabinets__label', label=_('Cabinets')
|
field='cabinets__label', label=_('Cabinets')
|
||||||
)
|
)
|
||||||
|
document_version_page_search.add_model_field(
|
||||||
|
field='document_version__document__cabinets__label',
|
||||||
|
label=_('Cabinets')
|
||||||
|
)
|
||||||
|
|
||||||
menu_facet.bind_links(
|
menu_facet.bind_links(
|
||||||
links=(link_document_cabinet_list,), sources=(Document,)
|
links=(link_document_cabinet_list,), sources=(Document,)
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ from mayan.apps.common.apps import MayanAppConfig
|
|||||||
from mayan.apps.common.menus import (
|
from mayan.apps.common.menus import (
|
||||||
menu_facet, menu_list_facet, menu_object, menu_secondary
|
menu_facet, menu_list_facet, menu_object, menu_secondary
|
||||||
)
|
)
|
||||||
from mayan.apps.documents.search import document_page_search, document_search
|
from mayan.apps.documents.search import (
|
||||||
|
document_page_search, document_search, document_version_page_search
|
||||||
|
)
|
||||||
from mayan.apps.events.classes import ModelEventType
|
from mayan.apps.events.classes import ModelEventType
|
||||||
from mayan.apps.events.links import (
|
from mayan.apps.events.links import (
|
||||||
link_events_for_object, link_object_event_types_user_subcriptions_list
|
link_events_for_object, link_object_event_types_user_subcriptions_list
|
||||||
@@ -80,13 +82,17 @@ class DocumentCommentsApp(MayanAppConfig):
|
|||||||
SourceColumn(attribute='comment', source=Comment)
|
SourceColumn(attribute='comment', source=Comment)
|
||||||
|
|
||||||
document_page_search.add_model_field(
|
document_page_search.add_model_field(
|
||||||
field='document_version__document__comments__comment',
|
field='document__comments__comment',
|
||||||
label=_('Comments')
|
label=_('Comments')
|
||||||
)
|
)
|
||||||
document_search.add_model_field(
|
document_search.add_model_field(
|
||||||
field='comments__comment',
|
field='comments__comment',
|
||||||
label=_('Comments')
|
label=_('Comments')
|
||||||
)
|
)
|
||||||
|
document_version_page_search.add_model_field(
|
||||||
|
field='document_version__document__comments__comment',
|
||||||
|
label=_('Comments')
|
||||||
|
)
|
||||||
|
|
||||||
menu_facet.bind_links(
|
menu_facet.bind_links(
|
||||||
links=(link_comments_for_document,), sources=(Document,)
|
links=(link_comments_for_document,), sources=(Document,)
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ from __future__ import unicode_literals
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
DocumentPageContent, DocumentVersionParseError
|
DocumentVersionPageContent, DocumentVersionParseError
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(DocumentPageContent)
|
@admin.register(DocumentVersionPageContent)
|
||||||
class DocumentPageContentAdmin(admin.ModelAdmin):
|
class DocumentVersionPageContentAdmin(admin.ModelAdmin):
|
||||||
list_display = ('document_page',)
|
list_display = ('document_version_page',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(DocumentVersionParseError)
|
@admin.register(DocumentVersionParseError)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from rest_framework.response import Response
|
|||||||
from mayan.apps.documents.models import Document
|
from mayan.apps.documents.models import Document
|
||||||
from mayan.apps.rest_api.permissions import MayanPermission
|
from mayan.apps.rest_api.permissions import MayanPermission
|
||||||
|
|
||||||
from .models import DocumentPageContent
|
from .models import DocumentVersionPageContent
|
||||||
from .permissions import permission_content_view
|
from .permissions import permission_content_view
|
||||||
from .serializers import DocumentPageContentSerializer
|
from .serializers import DocumentPageContentSerializer
|
||||||
|
|
||||||
@@ -41,8 +41,8 @@ class APIDocumentPageContentView(generics.RetrieveAPIView):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
content = instance.content
|
content = instance.content
|
||||||
except DocumentPageContent.DoesNotExist:
|
except DocumentVersionPageContent.DoesNotExist:
|
||||||
content = DocumentPageContent.objects.none()
|
content = DocumentVersionPageContent.objects.none()
|
||||||
|
|
||||||
serializer = self.get_serializer(content)
|
serializer = self.get_serializer(content)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ from mayan.apps.common.classes import ModelField
|
|||||||
from mayan.apps.common.menus import (
|
from mayan.apps.common.menus import (
|
||||||
menu_facet, menu_list_facet, menu_multi_item, menu_secondary, menu_tools
|
menu_facet, menu_list_facet, menu_multi_item, menu_secondary, menu_tools
|
||||||
)
|
)
|
||||||
from mayan.apps.documents.search import document_search, document_page_search
|
from mayan.apps.documents.search import (
|
||||||
|
document_search, document_page_search, document_version_page_search
|
||||||
|
)
|
||||||
from mayan.apps.documents.signals import post_version_upload
|
from mayan.apps.documents.signals import post_version_upload
|
||||||
from mayan.apps.events.classes import ModelEventType
|
from mayan.apps.events.classes import ModelEventType
|
||||||
from mayan.apps.navigation.classes import SourceColumn
|
from mayan.apps.navigation.classes import SourceColumn
|
||||||
@@ -43,7 +45,7 @@ from .permissions import (
|
|||||||
permission_parse_document
|
permission_parse_document
|
||||||
)
|
)
|
||||||
from .signals import post_document_version_parsing
|
from .signals import post_document_version_parsing
|
||||||
from .utils import get_document_content
|
from .utils import get_document_content, get_document_version_content
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -74,6 +76,9 @@ class DocumentParsingApp(MayanAppConfig):
|
|||||||
DocumentVersion = apps.get_model(
|
DocumentVersion = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentVersion'
|
app_label='documents', model_name='DocumentVersion'
|
||||||
)
|
)
|
||||||
|
DocumentVersionPage = apps.get_model(
|
||||||
|
app_label='documents', model_name='DocumentVersionPage'
|
||||||
|
)
|
||||||
DocumentVersionParseError = self.get_model(
|
DocumentVersionParseError = self.get_model(
|
||||||
model_name='DocumentVersionParseError'
|
model_name='DocumentVersionParseError'
|
||||||
)
|
)
|
||||||
@@ -85,7 +90,7 @@ class DocumentParsingApp(MayanAppConfig):
|
|||||||
name='content', value=get_document_content
|
name='content', value=get_document_content
|
||||||
)
|
)
|
||||||
DocumentVersion.add_to_class(
|
DocumentVersion.add_to_class(
|
||||||
name='content', value=get_document_content
|
name='content', value=get_document_version_content
|
||||||
)
|
)
|
||||||
DocumentVersion.add_to_class(
|
DocumentVersion.add_to_class(
|
||||||
name='submit_for_parsing',
|
name='submit_for_parsing',
|
||||||
@@ -100,9 +105,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,17 +138,17 @@ 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_version_page_search.add_model_field(
|
||||||
field='content__content', label=_('Content')
|
field='content__content', label=_('Content')
|
||||||
)
|
)
|
||||||
|
|
||||||
menu_facet.bind_links(
|
menu_facet.bind_links(
|
||||||
links=(link_document_content,), sources=(Document,)
|
links=(link_document_content,), sources=(Document,)
|
||||||
)
|
)
|
||||||
menu_facet.bind_links(
|
menu_list_facet.bind_links(
|
||||||
links=(link_document_page_content,), sources=(DocumentPage,)
|
links=(link_document_page_content,), sources=(DocumentPage,)
|
||||||
)
|
)
|
||||||
menu_list_facet.bind_links(
|
menu_list_facet.bind_links(
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _, ugettext
|
|||||||
|
|
||||||
from mayan.apps.common.widgets import TextAreaDiv
|
from mayan.apps.common.widgets import TextAreaDiv
|
||||||
|
|
||||||
from .models import DocumentPageContent
|
from .models import DocumentVersionPageContent
|
||||||
|
|
||||||
|
|
||||||
class DocumentContentForm(forms.Form):
|
class DocumentContentForm(forms.Form):
|
||||||
@@ -26,10 +26,10 @@ class DocumentContentForm(forms.Form):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
document_pages = []
|
document_pages = []
|
||||||
|
|
||||||
for page in document_pages:
|
for document_page in document_pages:
|
||||||
try:
|
try:
|
||||||
page_content = page.content.content
|
page_content = document_page.content_object.content.content
|
||||||
except DocumentPageContent.DoesNotExist:
|
except DocumentVersionPageContent.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
content.append(conditional_escape(force_text(page_content)))
|
content.append(conditional_escape(force_text(page_content)))
|
||||||
@@ -37,7 +37,7 @@ class DocumentContentForm(forms.Form):
|
|||||||
'\n\n\n<hr/><div class="document-page-content-divider">- %s -</div><hr/>\n\n\n' % (
|
'\n\n\n<hr/><div class="document-page-content-divider">- %s -</div><hr/>\n\n\n' % (
|
||||||
ugettext(
|
ugettext(
|
||||||
'Page %(page_number)d'
|
'Page %(page_number)d'
|
||||||
) % {'page_number': page.page_number}
|
) % {'page_number': document_page.page_number}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -72,8 +72,8 @@ class DocumentPageContentForm(forms.Form):
|
|||||||
self.fields['contents'].initial = ''
|
self.fields['contents'].initial = ''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
page_content = document_page.content.content
|
page_content = document_page.content_object.content.content
|
||||||
except DocumentPageContent.DoesNotExist:
|
except DocumentVersionPageContent.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
content = conditional_escape(force_text(page_content))
|
content = conditional_escape(force_text(page_content))
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ icon_document_content_download = Icon(
|
|||||||
icon_document_multiple_submit = Icon(
|
icon_document_multiple_submit = Icon(
|
||||||
driver_name='fontawesome', symbol='font'
|
driver_name='fontawesome', symbol='font'
|
||||||
)
|
)
|
||||||
|
icon_document_page_content = Icon(
|
||||||
|
driver_name='fontawesome', symbol='font'
|
||||||
|
)
|
||||||
icon_document_submit = Icon(
|
icon_document_submit = Icon(
|
||||||
driver_name='fontawesome', symbol='font'
|
driver_name='fontawesome', symbol='font'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -32,9 +32,15 @@ link_document_content_delete_multiple = Link(
|
|||||||
text=_('Delete parsed content'),
|
text=_('Delete parsed content'),
|
||||||
view='document_parsing:document_content_delete_multiple',
|
view='document_parsing:document_content_delete_multiple',
|
||||||
)
|
)
|
||||||
|
link_document_content_download = Link(
|
||||||
|
args='resolved_object.id',
|
||||||
|
icon_class_path='mayan.apps.document_parsing.icons.icon_document_content_download',
|
||||||
|
permissions=(permission_content_view,), text=_('Download content'),
|
||||||
|
view='document_parsing:document_content_download'
|
||||||
|
)
|
||||||
link_document_page_content = Link(
|
link_document_page_content = Link(
|
||||||
args='resolved_object.id', conditional_disable=is_document_page_disabled,
|
args='resolved_object.id', conditional_disable=is_document_page_disabled,
|
||||||
icon_class_path='mayan.apps.document_parsing.icons.icon_document_content',
|
icon_class_path='mayan.apps.document_parsing.icons.icon_document_page_content',
|
||||||
permissions=(permission_content_view,), text=_('Content'),
|
permissions=(permission_content_view,), text=_('Content'),
|
||||||
view='document_parsing:document_page_content'
|
view='document_parsing:document_page_content'
|
||||||
)
|
)
|
||||||
@@ -44,12 +50,6 @@ link_document_parsing_errors_list = Link(
|
|||||||
permissions=(permission_content_view,), text=_('Parsing errors'),
|
permissions=(permission_content_view,), text=_('Parsing errors'),
|
||||||
view='document_parsing:document_parsing_error_list'
|
view='document_parsing:document_parsing_error_list'
|
||||||
)
|
)
|
||||||
link_document_content_download = Link(
|
|
||||||
args='resolved_object.id',
|
|
||||||
icon_class_path='mayan.apps.document_parsing.icons.icon_document_content_download',
|
|
||||||
permissions=(permission_content_view,), text=_('Download content'),
|
|
||||||
view='document_parsing:document_content_download'
|
|
||||||
)
|
|
||||||
link_document_submit_multiple = Link(
|
link_document_submit_multiple = Link(
|
||||||
icon_class_path='mayan.apps.document_parsing.icons.icon_document_submit',
|
icon_class_path='mayan.apps.document_parsing.icons.icon_document_submit',
|
||||||
text=_('Submit for parsing'),
|
text=_('Submit for parsing'),
|
||||||
|
|||||||
@@ -18,11 +18,13 @@ from .signals import post_document_version_parsing
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DocumentPageContentManager(models.Manager):
|
class DocumentVersionPageContentManager(models.Manager):
|
||||||
def delete_content_for(self, document, user=None):
|
def delete_content_for(self, document, user=None):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
for document_page in document.pages.all():
|
for document_page in document.pages.all():
|
||||||
self.filter(document_page=document_page).delete()
|
self.filter(
|
||||||
|
document_version_page=document_page.content_object
|
||||||
|
).delete()
|
||||||
|
|
||||||
event_parsing_document_content_deleted.commit(
|
event_parsing_document_content_deleted.commit(
|
||||||
actor=user, target=document
|
actor=user, target=document
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ class Migration(migrations.Migration):
|
|||||||
('documents', '0041_auto_20170823_1855'),
|
('documents', '0041_auto_20170823_1855'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
run_before = [
|
||||||
|
('documents', '0052_rename_document_page'),
|
||||||
|
]
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='DocumentPageContent',
|
name='DocumentPageContent',
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('document_parsing', '0004_auto_20180917_0645'),
|
||||||
|
('documents', '0052_rename_document_page'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameModel(
|
||||||
|
'DocumentPageContent', 'DocumentVersionPageContent'
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='documentversionpagecontent',
|
||||||
|
name='document_page',
|
||||||
|
field=models.OneToOneField(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name='content',
|
||||||
|
to='documents.DocumentVersionPage',
|
||||||
|
verbose_name='Document version page'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='documentversionpagecontent',
|
||||||
|
old_name='document_page',
|
||||||
|
new_name='document_version_page',
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='documentversionpagecontent',
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Document version page content',
|
||||||
|
'verbose_name_plural': 'Document version pages contents'
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -5,36 +5,12 @@ 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 (
|
from mayan.apps.documents.models import (
|
||||||
DocumentPage, DocumentType, DocumentVersion
|
DocumentPage, DocumentType, DocumentVersion, DocumentVersionPage
|
||||||
)
|
)
|
||||||
|
|
||||||
from .managers import DocumentPageContentManager, DocumentTypeSettingsManager
|
from .managers import (
|
||||||
|
DocumentVersionPageContentManager, DocumentTypeSettingsManager
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class DocumentPageContent(models.Model):
|
|
||||||
"""
|
|
||||||
This model store's the parsed content of a document page.
|
|
||||||
"""
|
|
||||||
document_page = models.OneToOneField(
|
|
||||||
on_delete=models.CASCADE, related_name='content', to=DocumentPage,
|
|
||||||
verbose_name=_('Document page')
|
|
||||||
)
|
)
|
||||||
content = models.TextField(
|
|
||||||
blank=True, help_text=_(
|
|
||||||
'The actual text content as extracted by the document '
|
|
||||||
'parsing backend.'
|
|
||||||
), verbose_name=_('Content')
|
|
||||||
)
|
|
||||||
|
|
||||||
objects = DocumentPageContentManager()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('Document page content')
|
|
||||||
verbose_name_plural = _('Document pages contents')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return force_text(self.document_page)
|
|
||||||
|
|
||||||
|
|
||||||
class DocumentTypeSettings(models.Model):
|
class DocumentTypeSettings(models.Model):
|
||||||
@@ -62,6 +38,32 @@ class DocumentTypeSettings(models.Model):
|
|||||||
verbose_name_plural = _('Document types settings')
|
verbose_name_plural = _('Document types settings')
|
||||||
|
|
||||||
|
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
class DocumentVersionPageContent(models.Model):
|
||||||
|
"""
|
||||||
|
This model store's the parsed content of a document page.
|
||||||
|
"""
|
||||||
|
document_version_page = models.OneToOneField(
|
||||||
|
on_delete=models.CASCADE, related_name='content',
|
||||||
|
to=DocumentVersionPage, verbose_name=_('Document version page')
|
||||||
|
)
|
||||||
|
content = models.TextField(
|
||||||
|
blank=True, help_text=_(
|
||||||
|
'The actual text content as extracted by the document '
|
||||||
|
'parsing backend.'
|
||||||
|
), verbose_name=_('Content')
|
||||||
|
)
|
||||||
|
|
||||||
|
objects = DocumentVersionPageContentManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Document version page content')
|
||||||
|
verbose_name_plural = _('Document version pages contents')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return force_text(self.document_page)
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class DocumentVersionParseError(models.Model):
|
class DocumentVersionParseError(models.Model):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -23,11 +23,13 @@ class Parser(object):
|
|||||||
_registry = {}
|
_registry = {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_document_page(cls, document_page):
|
def parse_document_version_page(cls, document_version_page):
|
||||||
for parser_class in cls._registry.get(document_page.document_version.mimetype, ()):
|
for parser_class in cls._registry.get(document_version_page.document_version.mimetype, ()):
|
||||||
try:
|
try:
|
||||||
parser = parser_class()
|
parser = parser_class()
|
||||||
parser.process_document_page(document_page)
|
parser.process_document_page(
|
||||||
|
document_version_page=document_version_page
|
||||||
|
)
|
||||||
except ParserError:
|
except ParserError:
|
||||||
# If parser raises error, try next parser in the list
|
# If parser raises error, try next parser in the list
|
||||||
pass
|
pass
|
||||||
@@ -41,7 +43,9 @@ class Parser(object):
|
|||||||
for parser_class in cls._registry.get(document_version.mimetype, ()):
|
for parser_class in cls._registry.get(document_version.mimetype, ()):
|
||||||
try:
|
try:
|
||||||
parser = parser_class()
|
parser = parser_class()
|
||||||
parser.process_document_version(document_version)
|
parser.process_document_version(
|
||||||
|
document_version=document_version
|
||||||
|
)
|
||||||
except ParserError:
|
except ParserError:
|
||||||
# If parser raises error, try next parser in the list
|
# If parser raises error, try next parser in the list
|
||||||
pass
|
pass
|
||||||
@@ -64,29 +68,33 @@ class Parser(object):
|
|||||||
)
|
)
|
||||||
logger.debug('document version: %d', document_version.pk)
|
logger.debug('document version: %d', document_version.pk)
|
||||||
|
|
||||||
for document_page in document_version.pages.all():
|
for document_version_page in document_version.pages.all():
|
||||||
self.process_document_page(document_page=document_page)
|
self.process_document_version_page(
|
||||||
|
document_version_page=document_version_page
|
||||||
|
)
|
||||||
|
|
||||||
def process_document_page(self, document_page):
|
def process_document_version_page(self, document_version_page):
|
||||||
DocumentPageContent = apps.get_model(
|
DocumentVersionPageContent = apps.get_model(
|
||||||
app_label='document_parsing', model_name='DocumentPageContent'
|
app_label='document_parsing',
|
||||||
|
model_name='DocumentVersionPageContent'
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
'Processing page: %d of document version: %s',
|
'Processing page: %d of document version: %s',
|
||||||
document_page.page_number, document_page.document_version
|
document_version_page.page_number,
|
||||||
|
document_version_page.document_version
|
||||||
)
|
)
|
||||||
|
|
||||||
file_object = document_page.document_version.get_intermediate_file()
|
file_object = document_version_page.document_version.get_intermediate_file()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
document_page_content, created = DocumentPageContent.objects.get_or_create(
|
document_version_page_content, created = DocumentVersionPageContent.objects.get_or_create(
|
||||||
document_page=document_page
|
document_version_page=document_version_page
|
||||||
)
|
)
|
||||||
document_page_content.content = self.execute(
|
document_version_page_content.content = self.execute(
|
||||||
file_object=file_object, page_number=document_page.page_number
|
file_object=file_object, page_number=document_version_page.page_number
|
||||||
)
|
)
|
||||||
document_page_content.save()
|
document_version_page_content.save()
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
error_message = _('Exception parsing page; %s') % exception
|
error_message = _('Exception parsing page; %s') % exception
|
||||||
logger.error(error_message)
|
logger.error(error_message)
|
||||||
@@ -96,7 +104,8 @@ class Parser(object):
|
|||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
'Finished processing page: %d of document version: %s',
|
'Finished processing page: %d of document version: %s',
|
||||||
document_page.page_number, document_page.document_version
|
document_version_page.page_number,
|
||||||
|
document_version_page.document_version
|
||||||
)
|
)
|
||||||
|
|
||||||
def execute(self, file_object, page_number):
|
def execute(self, file_object, page_number):
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from .models import DocumentPageContent
|
from .models import DocumentVersionPageContent
|
||||||
|
|
||||||
|
|
||||||
class DocumentPageContentSerializer(serializers.ModelSerializer):
|
class DocumentPageContentSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = ('content',)
|
fields = ('content',)
|
||||||
model = DocumentPageContent
|
model = DocumentVersionPageContent
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ def task_parse_document_version(document_version_pk):
|
|||||||
DocumentVersion = apps.get_model(
|
DocumentVersion = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentVersion'
|
app_label='documents', model_name='DocumentVersion'
|
||||||
)
|
)
|
||||||
DocumentPageContent = apps.get_model(
|
DocumentVersionPageContent = apps.get_model(
|
||||||
app_label='document_parsing', model_name='DocumentPageContent'
|
app_label='document_parsing', model_name='DocumentVersionPageContent'
|
||||||
)
|
)
|
||||||
|
|
||||||
document_version = DocumentVersion.objects.get(
|
document_version = DocumentVersion.objects.get(
|
||||||
@@ -24,6 +24,6 @@ def task_parse_document_version(document_version_pk):
|
|||||||
logger.info(
|
logger.info(
|
||||||
'Starting parsing for document version: %s', document_version
|
'Starting parsing for document version: %s', document_version
|
||||||
)
|
)
|
||||||
DocumentPageContent.objects.process_document_version(
|
DocumentVersionPageContent.objects.process_document_version(
|
||||||
document_version=document_version
|
document_version=document_version
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from ..events import (
|
|||||||
event_parsing_document_version_submit,
|
event_parsing_document_version_submit,
|
||||||
event_parsing_document_version_finish
|
event_parsing_document_version_finish
|
||||||
)
|
)
|
||||||
from ..models import DocumentPageContent
|
from ..models import DocumentVersionPageContent
|
||||||
|
|
||||||
|
|
||||||
class DocumentParsingEventsTestCase(GenericDocumentTestCase):
|
class DocumentParsingEventsTestCase(GenericDocumentTestCase):
|
||||||
@@ -19,7 +19,7 @@ class DocumentParsingEventsTestCase(GenericDocumentTestCase):
|
|||||||
|
|
||||||
def test_document_content_deleted_event(self):
|
def test_document_content_deleted_event(self):
|
||||||
Action.objects.all().delete()
|
Action.objects.all().delete()
|
||||||
DocumentPageContent.objects.delete_content_for(
|
DocumentVersionPageContent.objects.delete_content_for(
|
||||||
document=self.test_document
|
document=self.test_document
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -18,5 +18,5 @@ class ParserTestCase(DocumentTestMixin, BaseTestCase):
|
|||||||
parser.process_document_version(self.test_document.latest_version)
|
parser.process_document_version(self.test_document.latest_version)
|
||||||
|
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
TEST_DOCUMENT_CONTENT in self.test_document.pages.first().content.content
|
TEST_DOCUMENT_CONTENT in self.test_document.pages.first().content_object.content.content
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from django.test import override_settings
|
|||||||
from mayan.apps.documents.tests.base import GenericDocumentViewTestCase
|
from mayan.apps.documents.tests.base import GenericDocumentViewTestCase
|
||||||
from mayan.apps.documents.tests.literals import TEST_HYBRID_DOCUMENT
|
from mayan.apps.documents.tests.literals import TEST_HYBRID_DOCUMENT
|
||||||
|
|
||||||
from ..models import DocumentPageContent
|
from ..models import DocumentVersionPageContent
|
||||||
from ..permissions import (
|
from ..permissions import (
|
||||||
permission_content_view, permission_document_type_parsing_setup,
|
permission_content_view, permission_document_type_parsing_setup,
|
||||||
permission_parse_document
|
permission_parse_document
|
||||||
@@ -72,8 +72,8 @@ class DocumentContentViewsTestCase(
|
|||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
DocumentPageContent.objects.filter(
|
DocumentVersionPageContent.objects.filter(
|
||||||
document_page=self.test_document.pages.first()
|
document_version_page=self.test_document.pages.first().content_object
|
||||||
).exists()
|
).exists()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -86,8 +86,8 @@ class DocumentContentViewsTestCase(
|
|||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
DocumentPageContent.objects.filter(
|
DocumentVersionPageContent.objects.filter(
|
||||||
document_page=self.test_document.pages.first()
|
document_version_page=self.test_document.pages.first().content_object
|
||||||
).exists()
|
).exists()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ from .views import (
|
|||||||
DocumentContentView, DocumentContentDeleteView,
|
DocumentContentView, DocumentContentDeleteView,
|
||||||
DocumentContentDownloadView, DocumentPageContentView,
|
DocumentContentDownloadView, DocumentPageContentView,
|
||||||
DocumentParsingErrorsListView, DocumentSubmitView,
|
DocumentParsingErrorsListView, DocumentSubmitView,
|
||||||
DocumentTypeSettingsEditView, DocumentTypeSubmitView, ParseErrorListView
|
DocumentTypeSettingsEditView, DocumentTypeSubmitView,
|
||||||
|
DocumentVersionPageContentView,
|
||||||
|
ParseErrorListView
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@@ -34,6 +36,11 @@ urlpatterns = [
|
|||||||
regex=r'^documents/pages/(?P<pk>\d+)/content/$',
|
regex=r'^documents/pages/(?P<pk>\d+)/content/$',
|
||||||
view=DocumentPageContentView.as_view(), name='document_page_content'
|
view=DocumentPageContentView.as_view(), name='document_page_content'
|
||||||
),
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^documents/versions/pages/(?P<pk>\d+)/content/$',
|
||||||
|
view=DocumentVersionPageContentView.as_view(),
|
||||||
|
name='document_version_page_content'
|
||||||
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^documents/(?P<pk>\d+)/submit/$',
|
regex=r'^documents/(?P<pk>\d+)/submit/$',
|
||||||
view=DocumentSubmitView.as_view(), name='document_submit'
|
view=DocumentSubmitView.as_view(), name='document_submit'
|
||||||
|
|||||||
@@ -6,14 +6,28 @@ from django.utils.html import conditional_escape
|
|||||||
|
|
||||||
|
|
||||||
def get_document_content(document):
|
def get_document_content(document):
|
||||||
DocumentPageContent = apps.get_model(
|
DocumentVersionPageContent = apps.get_model(
|
||||||
app_label='document_parsing', model_name='DocumentPageContent'
|
app_label='document_parsing', model_name='DocumentVersionPageContent'
|
||||||
)
|
)
|
||||||
|
|
||||||
for page in document.pages.all():
|
for document_page in document.pages.all():
|
||||||
try:
|
try:
|
||||||
page_content = page.content.content
|
page_content = document_page.content_object.content.content
|
||||||
except DocumentPageContent.DoesNotExist:
|
except DocumentVersionPageContent.DoesNotExist:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
yield conditional_escape(force_text(page_content))
|
||||||
|
|
||||||
|
|
||||||
|
def get_document_version_content(document_version):
|
||||||
|
DocumentVersionPageContent = apps.get_model(
|
||||||
|
app_label='document_parsing', model_name='DocumentVersionPageContent'
|
||||||
|
)
|
||||||
|
|
||||||
|
for document_version_page in document_version.pages.all():
|
||||||
|
try:
|
||||||
|
page_content = document_version_page.content.content
|
||||||
|
except DocumentVersionPageContent.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
yield conditional_escape(force_text(page_content))
|
yield conditional_escape(force_text(page_content))
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ 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, DocumentPage, DocumentType, DocumentVersionPage
|
||||||
|
)
|
||||||
|
|
||||||
from .forms import DocumentContentForm, DocumentPageContentForm
|
from .forms import DocumentContentForm, DocumentPageContentForm
|
||||||
from .models import DocumentPageContent, DocumentVersionParseError
|
from .models import DocumentVersionPageContent, DocumentVersionParseError
|
||||||
from .permissions import (
|
from .permissions import (
|
||||||
permission_content_view, permission_document_type_parsing_setup,
|
permission_content_view, permission_document_type_parsing_setup,
|
||||||
permission_parse_document
|
permission_parse_document
|
||||||
@@ -46,7 +48,7 @@ class DocumentContentDeleteView(MultipleObjectConfirmActionView):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def object_action(self, form, instance):
|
def object_action(self, form, instance):
|
||||||
DocumentPageContent.objects.delete_content_for(
|
DocumentVersionPageContent.objects.delete_content_for(
|
||||||
document=instance, user=self.request.user
|
document=instance, user=self.request.user
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -107,6 +109,30 @@ class DocumentPageContentView(SingleObjectDetailView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentVersionPageContentView(SingleObjectDetailView):
|
||||||
|
form_class = DocumentPageContentForm
|
||||||
|
model = DocumentVersionPage
|
||||||
|
object_permission = permission_content_view
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
result = super(DocumentPageContentView, self).dispatch(
|
||||||
|
request, *args, **kwargs
|
||||||
|
)
|
||||||
|
self.get_object().document.add_as_recent_document_for_user(
|
||||||
|
request.user
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'hide_labels': True,
|
||||||
|
'object': self.get_object(),
|
||||||
|
'title': _(
|
||||||
|
'Content for document version page: %s'
|
||||||
|
) % self.get_object(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class DocumentParsingErrorsListView(SingleObjectListView):
|
class DocumentParsingErrorsListView(SingleObjectListView):
|
||||||
view_permission = permission_content_view
|
view_permission = permission_content_view
|
||||||
|
|
||||||
|
|||||||
@@ -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,10 +33,14 @@ 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,
|
||||||
|
task_generate_document_version_page_image
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -168,13 +172,8 @@ class APIDocumentPageImageView(generics.RetrieveAPIView):
|
|||||||
)
|
)
|
||||||
return document
|
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):
|
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 +220,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()
|
||||||
|
|
||||||
|
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 +318,33 @@ 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_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
|
||||||
@@ -423,7 +538,7 @@ class APIRecentDocumentListView(generics.ListAPIView):
|
|||||||
|
|
||||||
|
|
||||||
class APIDocumentVersionPageListView(generics.ListAPIView):
|
class APIDocumentVersionPageListView(generics.ListAPIView):
|
||||||
serializer_class = DocumentPageSerializer
|
serializer_class = DocumentVersionPageSerializer
|
||||||
|
|
||||||
def get_document(self):
|
def get_document(self):
|
||||||
document = get_object_or_404(Document, pk=self.kwargs['pk'])
|
document = get_object_or_404(Document, pk=self.kwargs['pk'])
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ from .links import (
|
|||||||
link_document_multiple_delete, link_document_multiple_document_type_edit,
|
link_document_multiple_delete, link_document_multiple_document_type_edit,
|
||||||
link_document_multiple_download, link_document_multiple_favorites_add,
|
link_document_multiple_download, link_document_multiple_favorites_add,
|
||||||
link_document_multiple_favorites_remove, link_document_multiple_restore,
|
link_document_multiple_favorites_remove, link_document_multiple_restore,
|
||||||
link_document_multiple_trash, link_document_multiple_update_page_count,
|
link_document_multiple_trash, link_document_multiple_pages_reset,
|
||||||
link_document_page_disable, link_document_page_multiple_disable,
|
link_document_page_disable, link_document_page_multiple_disable,
|
||||||
link_document_page_enable, link_document_page_multiple_enable,
|
link_document_page_enable, link_document_page_multiple_enable,
|
||||||
link_document_page_navigation_first, link_document_page_navigation_last,
|
link_document_page_navigation_first, link_document_page_navigation_last,
|
||||||
@@ -74,8 +74,10 @@ from .links import (
|
|||||||
link_document_type_filename_create, link_document_type_filename_delete,
|
link_document_type_filename_create, link_document_type_filename_delete,
|
||||||
link_document_type_filename_edit, link_document_type_filename_list,
|
link_document_type_filename_edit, link_document_type_filename_list,
|
||||||
link_document_type_list, link_document_type_policies,
|
link_document_type_list, link_document_type_policies,
|
||||||
link_document_type_setup, link_document_update_page_count,
|
link_document_type_setup, link_document_pages_reset,
|
||||||
link_document_version_download, link_document_version_list,
|
link_document_version_download, link_document_version_list,
|
||||||
|
link_document_version_multiple_page_count_update,
|
||||||
|
link_document_version_page_count_update,
|
||||||
link_document_version_return_document, link_document_version_return_list,
|
link_document_version_return_document, link_document_version_return_list,
|
||||||
link_document_version_revert, link_document_version_view,
|
link_document_version_revert, link_document_version_view,
|
||||||
link_duplicated_document_list, link_duplicated_document_scan,
|
link_duplicated_document_list, link_duplicated_document_scan,
|
||||||
@@ -87,10 +89,10 @@ from .permissions import (
|
|||||||
permission_document_download, permission_document_edit,
|
permission_document_download, permission_document_edit,
|
||||||
permission_document_new_version, permission_document_print,
|
permission_document_new_version, permission_document_print,
|
||||||
permission_document_properties_edit, permission_document_restore,
|
permission_document_properties_edit, permission_document_restore,
|
||||||
permission_document_trash, permission_document_type_delete,
|
permission_document_tools, permission_document_trash,
|
||||||
permission_document_type_edit, permission_document_type_view,
|
permission_document_type_delete, permission_document_type_edit,
|
||||||
permission_document_version_revert, permission_document_version_view,
|
permission_document_type_view, permission_document_version_revert,
|
||||||
permission_document_view
|
permission_document_version_view, permission_document_view,
|
||||||
)
|
)
|
||||||
# Just import to initialize the search models
|
# Just import to initialize the search models
|
||||||
from .search import document_search, document_page_search # NOQA
|
from .search import document_search, document_page_search # NOQA
|
||||||
@@ -121,10 +123,11 @@ 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='DocumentPage')
|
||||||
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')
|
||||||
|
DocumentVersionPage = self.get_model(model_name='DocumentVersionPage')
|
||||||
DuplicatedDocument = self.get_model(model_name='DuplicatedDocument')
|
DuplicatedDocument = self.get_model(model_name='DuplicatedDocument')
|
||||||
|
|
||||||
DynamicSerializerField.add_serializer(
|
DynamicSerializerField.add_serializer(
|
||||||
@@ -190,13 +193,15 @@ class DocumentsApp(MayanAppConfig):
|
|||||||
permission_acl_edit, permission_acl_view,
|
permission_acl_edit, permission_acl_view,
|
||||||
permission_document_delete, permission_document_download,
|
permission_document_delete, permission_document_download,
|
||||||
permission_document_edit, permission_document_new_version,
|
permission_document_edit, permission_document_new_version,
|
||||||
permission_document_print, permission_document_properties_edit,
|
permission_document_print,
|
||||||
permission_document_restore, permission_document_trash,
|
permission_document_properties_edit,
|
||||||
permission_document_version_revert,
|
permission_document_restore, permission_document_tools,
|
||||||
|
permission_document_trash, permission_document_version_revert,
|
||||||
permission_document_version_view, permission_document_view,
|
permission_document_version_view, permission_document_view,
|
||||||
permission_events_view, permission_transformation_create,
|
permission_events_view, permission_transformation_create,
|
||||||
permission_transformation_delete,
|
permission_transformation_delete,
|
||||||
permission_transformation_edit, permission_transformation_view,
|
permission_transformation_edit,
|
||||||
|
permission_transformation_view,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -224,13 +229,13 @@ class DocumentsApp(MayanAppConfig):
|
|||||||
model=Document, manager_name='passthrough'
|
model=Document, manager_name='passthrough'
|
||||||
)
|
)
|
||||||
ModelPermission.register_inheritance(
|
ModelPermission.register_inheritance(
|
||||||
model=DocumentPage, related='document_version__document',
|
model=DocumentPage, related='document',
|
||||||
)
|
)
|
||||||
ModelPermission.register_manager(
|
ModelPermission.register_manager(
|
||||||
model=DocumentPage, manager_name='passthrough'
|
model=DocumentPage, manager_name='passthrough'
|
||||||
)
|
)
|
||||||
ModelPermission.register_inheritance(
|
ModelPermission.register_inheritance(
|
||||||
model=DocumentPageResult, related='document_version__document',
|
model=DocumentPageResult, related='document',
|
||||||
)
|
)
|
||||||
ModelPermission.register_manager(
|
ModelPermission.register_manager(
|
||||||
model=DocumentPageResult, manager_name='passthrough'
|
model=DocumentPageResult, manager_name='passthrough'
|
||||||
@@ -241,6 +246,9 @@ class DocumentsApp(MayanAppConfig):
|
|||||||
ModelPermission.register_inheritance(
|
ModelPermission.register_inheritance(
|
||||||
model=DocumentVersion, related='document',
|
model=DocumentVersion, related='document',
|
||||||
)
|
)
|
||||||
|
ModelPermission.register_inheritance(
|
||||||
|
model=DocumentVersionPage, related='document_version',
|
||||||
|
)
|
||||||
|
|
||||||
# Document and document page thumbnail widget
|
# Document and document page thumbnail widget
|
||||||
document_page_thumbnail_widget = DocumentPageThumbnailWidget()
|
document_page_thumbnail_widget = DocumentPageThumbnailWidget()
|
||||||
@@ -454,7 +462,7 @@ class DocumentsApp(MayanAppConfig):
|
|||||||
link_document_quick_download, link_document_download,
|
link_document_quick_download, link_document_download,
|
||||||
link_document_clear_transformations,
|
link_document_clear_transformations,
|
||||||
link_document_clone_transformations,
|
link_document_clone_transformations,
|
||||||
link_document_update_page_count,
|
link_document_pages_reset,
|
||||||
), sources=(Document,)
|
), sources=(Document,)
|
||||||
)
|
)
|
||||||
menu_object.bind_links(
|
menu_object.bind_links(
|
||||||
@@ -495,7 +503,7 @@ class DocumentsApp(MayanAppConfig):
|
|||||||
link_document_multiple_favorites_remove,
|
link_document_multiple_favorites_remove,
|
||||||
link_document_multiple_clear_transformations,
|
link_document_multiple_clear_transformations,
|
||||||
link_document_multiple_trash, link_document_multiple_download,
|
link_document_multiple_trash, link_document_multiple_download,
|
||||||
link_document_multiple_update_page_count,
|
link_document_multiple_pages_reset,
|
||||||
link_document_multiple_document_type_edit,
|
link_document_multiple_document_type_edit,
|
||||||
), sources=(Document,)
|
), sources=(Document,)
|
||||||
)
|
)
|
||||||
@@ -547,6 +555,17 @@ class DocumentsApp(MayanAppConfig):
|
|||||||
link_document_version_return_list
|
link_document_version_return_list
|
||||||
), sources=(DocumentVersion,)
|
), sources=(DocumentVersion,)
|
||||||
)
|
)
|
||||||
|
menu_multi_item.bind_links(
|
||||||
|
links=(
|
||||||
|
link_document_version_multiple_page_count_update,
|
||||||
|
), sources=(DocumentVersion,)
|
||||||
|
)
|
||||||
|
menu_object.bind_links(
|
||||||
|
links=(
|
||||||
|
link_document_version_page_count_update,
|
||||||
|
), sources=(DocumentVersion,)
|
||||||
|
)
|
||||||
|
|
||||||
menu_list_facet.bind_links(
|
menu_list_facet.bind_links(
|
||||||
links=(link_document_version_view,), sources=(DocumentVersion,)
|
links=(link_document_version_view,), sources=(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)
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,10 @@ icon_document_edit = Icon(
|
|||||||
)
|
)
|
||||||
icon_document = Icon(driver_name='fontawesome', symbol='book')
|
icon_document = Icon(driver_name='fontawesome', symbol='book')
|
||||||
icon_document_list = icon_document
|
icon_document_list = icon_document
|
||||||
icon_document_page_count_update = Icon(
|
icon_document_pages_reset = Icon(
|
||||||
|
driver_name='fontawesome', symbol='copy'
|
||||||
|
)
|
||||||
|
icon_document_version_page_count_update = Icon(
|
||||||
driver_name='fontawesome', symbol='copy'
|
driver_name='fontawesome', symbol='copy'
|
||||||
)
|
)
|
||||||
icon_document_preview = Icon(driver_name='fontawesome', symbol='eye')
|
icon_document_preview = Icon(driver_name='fontawesome', symbol='eye')
|
||||||
|
|||||||
@@ -168,12 +168,12 @@ link_document_quick_download = Link(
|
|||||||
permissions=(permission_document_download,), text=_('Quick download'),
|
permissions=(permission_document_download,), text=_('Quick download'),
|
||||||
view='documents:document_download',
|
view='documents:document_download',
|
||||||
)
|
)
|
||||||
link_document_update_page_count = Link(
|
link_document_pages_reset = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.documents.icons.icon_document_page_count_update',
|
icon_class_path='mayan.apps.documents.icons.icon_document_pages_reset',
|
||||||
permissions=(permission_document_tools,),
|
permissions=(permission_document_tools,),
|
||||||
text=_('Recalculate page count'),
|
text=_('Reset pages'),
|
||||||
view='documents:document_update_page_count'
|
view='documents:document_pages_reset'
|
||||||
)
|
)
|
||||||
link_document_restore = Link(
|
link_document_restore = Link(
|
||||||
permissions=(permission_document_restore,),
|
permissions=(permission_document_restore,),
|
||||||
@@ -217,10 +217,10 @@ link_document_multiple_download = Link(
|
|||||||
text=_('Advanced download'),
|
text=_('Advanced download'),
|
||||||
view='documents:document_multiple_download_form'
|
view='documents:document_multiple_download_form'
|
||||||
)
|
)
|
||||||
link_document_multiple_update_page_count = Link(
|
link_document_multiple_pages_reset = Link(
|
||||||
icon_class_path='mayan.apps.documents.icons.icon_document_page_count_update',
|
icon_class_path='mayan.apps.documents.icons.icon_document_pages_reset',
|
||||||
text=_('Recalculate page count'),
|
text=_('Reset pages'),
|
||||||
view='documents:document_multiple_update_page_count'
|
view='documents:document_multiple_pages_reset'
|
||||||
)
|
)
|
||||||
link_document_multiple_restore = Link(
|
link_document_multiple_restore = Link(
|
||||||
icon_class_path='mayan.apps.documents.icons.icon_trashed_document_restore',
|
icon_class_path='mayan.apps.documents.icons.icon_trashed_document_restore',
|
||||||
@@ -246,6 +246,18 @@ link_document_version_return_list = Link(
|
|||||||
permissions=(permission_document_version_view,), text=_('Versions'),
|
permissions=(permission_document_version_view,), text=_('Versions'),
|
||||||
view='documents:document_version_list',
|
view='documents:document_version_list',
|
||||||
)
|
)
|
||||||
|
link_document_version_page_count_update = Link(
|
||||||
|
args='resolved_object.pk',
|
||||||
|
icon_class_path='mayan.apps.documents.icons.icon_document_version_page_count_update',
|
||||||
|
permissions=(permission_document_tools,),
|
||||||
|
text=_('Update page count'),
|
||||||
|
view='documents:document_version_page_count_update'
|
||||||
|
)
|
||||||
|
link_document_version_multiple_page_count_update = Link(
|
||||||
|
icon_class_path='mayan.apps.documents.icons.icon_document_version_page_count_update',
|
||||||
|
text=_('Update page count'),
|
||||||
|
view='documents:document_version_multiple_page_count_update'
|
||||||
|
)
|
||||||
link_document_version_view = Link(
|
link_document_version_view = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.documents.icons.icon_document_version_view',
|
icon_class_path='mayan.apps.documents.icons.icon_document_version_view',
|
||||||
|
|||||||
@@ -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,19 @@ 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)
|
||||||
|
|
||||||
|
|
||||||
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()
|
||||||
|
|||||||
38
mayan/apps/documents/migrations/0052_rename_document_page.py
Normal file
38
mayan/apps/documents/migrations/0052_rename_document_page.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
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='pages', to='documents.DocumentVersion',
|
||||||
|
verbose_name='Document version'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='documentversionpage',
|
||||||
|
name='enabled',
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='documentversionpage',
|
||||||
|
options={
|
||||||
|
'ordering': ('page_number',),
|
||||||
|
'verbose_name': 'Document version page',
|
||||||
|
'verbose_name_plural': 'Document version pages'
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
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', '0052_rename_document_page'),
|
||||||
|
]
|
||||||
|
|
||||||
|
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={
|
||||||
|
'unique_together': set([('document', 'page_number')]),
|
||||||
|
'verbose_name': 'Document page',
|
||||||
|
'verbose_name_plural': 'Document pages',
|
||||||
|
'ordering': ('page_number',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DocumentPageResult',
|
||||||
|
fields=[
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Document page result',
|
||||||
|
'verbose_name_plural': 'Document pages result',
|
||||||
|
'ordering': ('document', 'page_number'),
|
||||||
|
'proxy': True,
|
||||||
|
'indexes': [],
|
||||||
|
},
|
||||||
|
bases=('documents.documentpage',),
|
||||||
|
),
|
||||||
|
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',),
|
||||||
|
),
|
||||||
|
]
|
||||||
56
mayan/apps/documents/migrations/0054_reset_document_pages.py
Normal file
56
mayan/apps/documents/migrations/0054_reset_document_pages.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def get_latest_version(document):
|
||||||
|
return document.versions.order_by('timestamp').last()
|
||||||
|
|
||||||
|
|
||||||
|
def operation_reset_document_pages(apps, schema_editor):
|
||||||
|
Document = apps.get_model(app_label='documents', model_name='Document')
|
||||||
|
|
||||||
|
# Define inside the function to use the migration's apps instance
|
||||||
|
def pages_reset(document):
|
||||||
|
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||||
|
DocumentVersionPage = apps.get_model(
|
||||||
|
app_label='documents', model_name='DocumentVersionPage'
|
||||||
|
)
|
||||||
|
|
||||||
|
content_type = ContentType.objects.get_for_model(
|
||||||
|
model=DocumentVersionPage
|
||||||
|
)
|
||||||
|
|
||||||
|
for document_page in document.pages.all():
|
||||||
|
document_page.delete()
|
||||||
|
|
||||||
|
for version_page in get_latest_version(document=document).pages.all():
|
||||||
|
document_page = document.pages.create(
|
||||||
|
content_type=content_type,
|
||||||
|
page_number=version_page.page_number,
|
||||||
|
object_id=version_page.pk,
|
||||||
|
)
|
||||||
|
|
||||||
|
for document in Document.objects.using(schema_editor.connection.alias).all():
|
||||||
|
pages_reset(document=document)
|
||||||
|
|
||||||
|
|
||||||
|
def operation_reset_document_pages_reverse(apps, schema_editor):
|
||||||
|
Document = apps.get_model(app_label='documents', model_name='Document')
|
||||||
|
|
||||||
|
for document in Document.objects.using(schema_editor.connection.alias).all():
|
||||||
|
for document_page in document.pages.all():
|
||||||
|
document_page.delete()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('documents', '0053_create_document_page_and_result_models'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
code=operation_reset_document_pages,
|
||||||
|
reverse_code=operation_reset_document_pages_reverse
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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,26 @@ 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
|
||||||
|
|
||||||
|
@property
|
||||||
|
def checksum(self):
|
||||||
|
return self.latest_version.checksum
|
||||||
|
|
||||||
|
@property
|
||||||
|
def date_updated(self):
|
||||||
|
return self.latest_version.timestamp
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
to_trash = kwargs.pop('to_trash', True)
|
to_trash = kwargs.pop('to_trash', True)
|
||||||
|
|
||||||
@@ -126,25 +148,37 @@ class Document(models.Model):
|
|||||||
else:
|
else:
|
||||||
return False
|
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):
|
def get_absolute_url(self):
|
||||||
return reverse(
|
return reverse(
|
||||||
viewname='documents:document_preview', kwargs={'pk': self.pk}
|
viewname='documents:document_preview', kwargs={'pk': self.pk}
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_api_image_url(self, *args, **kwargs):
|
def get_api_image_url(self, *args, **kwargs):
|
||||||
latest_version = self.latest_version
|
first_page = self.pages.first()
|
||||||
if latest_version:
|
if first_page:
|
||||||
return latest_version.get_api_image_url(*args, **kwargs)
|
return first_page.get_api_image_url(*args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_in_trash(self):
|
def is_in_trash(self):
|
||||||
return self.in_trash
|
return self.in_trash
|
||||||
|
|
||||||
|
@property
|
||||||
|
def latest_version(self):
|
||||||
|
return self.versions.order_by('timestamp').last()
|
||||||
|
|
||||||
def natural_key(self):
|
def natural_key(self):
|
||||||
return (self.uuid,)
|
return (self.uuid,)
|
||||||
natural_key.dependencies = ['documents.DocumentType']
|
natural_key.dependencies = ['documents.DocumentType']
|
||||||
|
|
||||||
def new_version(self, file_object, comment=None, _user=None):
|
def new_version(self, file_object, append_pages=False, comment=None, _user=None):
|
||||||
logger.info('Creating new document version for document: %s', self)
|
logger.info('Creating new document version for document: %s', self)
|
||||||
DocumentVersion = apps.get_model(
|
DocumentVersion = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentVersion'
|
app_label='documents', model_name='DocumentVersion'
|
||||||
@@ -153,9 +187,10 @@ class Document(models.Model):
|
|||||||
document_version = DocumentVersion(
|
document_version = DocumentVersion(
|
||||||
document=self, comment=comment or '', file=File(file_object)
|
document=self, comment=comment or '', file=File(file_object)
|
||||||
)
|
)
|
||||||
document_version.save(_user=_user)
|
document_version.save(append_pages=append_pages, _user=_user)
|
||||||
|
|
||||||
logger.info('New document version queued for document: %s', self)
|
logger.info('New document version queued for document: %s', self)
|
||||||
|
|
||||||
return document_version
|
return document_version
|
||||||
|
|
||||||
def open(self, *args, **kwargs):
|
def open(self, *args, **kwargs):
|
||||||
@@ -165,6 +200,34 @@ class Document(models.Model):
|
|||||||
"""
|
"""
|
||||||
return self.latest_version.open(*args, **kwargs)
|
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():
|
||||||
|
self.pages.create(
|
||||||
|
content_object=version_page
|
||||||
|
)
|
||||||
|
|
||||||
def restore(self):
|
def restore(self):
|
||||||
self.in_trash = False
|
self.in_trash = False
|
||||||
self.save()
|
self.save()
|
||||||
@@ -209,53 +272,3 @@ class Document(models.Model):
|
|||||||
@property
|
@property
|
||||||
def size(self):
|
def size(self):
|
||||||
return self.latest_version.size
|
return self.latest_version.size
|
||||||
|
|
||||||
# Compatibility methods
|
|
||||||
|
|
||||||
@property
|
|
||||||
def checksum(self):
|
|
||||||
return self.latest_version.checksum
|
|
||||||
|
|
||||||
@property
|
|
||||||
def date_updated(self):
|
|
||||||
return self.latest_version.timestamp
|
|
||||||
|
|
||||||
@property
|
|
||||||
def file_mime_encoding(self):
|
|
||||||
return self.latest_version.encoding
|
|
||||||
|
|
||||||
@property
|
|
||||||
def file_mimetype(self):
|
|
||||||
return self.latest_version.mimetype
|
|
||||||
|
|
||||||
@property
|
|
||||||
def latest_version(self):
|
|
||||||
return self.versions.order_by('timestamp').last()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def page_count(self):
|
|
||||||
return self.latest_version.page_count
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pages_all(self):
|
|
||||||
try:
|
|
||||||
return self.latest_version.pages_all
|
|
||||||
except AttributeError:
|
|
||||||
# Document has no version yet
|
|
||||||
DocumentPage = apps.get_model(
|
|
||||||
app_label='documents', model_name='DocumentPage'
|
|
||||||
)
|
|
||||||
|
|
||||||
return DocumentPage.objects.none()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pages(self):
|
|
||||||
try:
|
|
||||||
return self.latest_version.pages
|
|
||||||
except AttributeError:
|
|
||||||
# Document has no version yet
|
|
||||||
DocumentPage = apps.get_model(
|
|
||||||
app_label='documents', model_name='DocumentPage'
|
|
||||||
)
|
|
||||||
|
|
||||||
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,
|
||||||
@@ -21,12 +23,11 @@ from mayan.apps.converter.utils import get_converter_class
|
|||||||
|
|
||||||
from ..managers import DocumentPageManager
|
from ..managers import DocumentPageManager
|
||||||
from ..settings import (
|
from ..settings import (
|
||||||
setting_disable_base_image_cache, setting_disable_transformed_image_cache,
|
|
||||||
setting_display_width, setting_display_height, setting_zoom_max_level,
|
setting_display_width, setting_display_height, setting_zoom_max_level,
|
||||||
setting_zoom_min_level
|
setting_zoom_min_level
|
||||||
)
|
)
|
||||||
|
|
||||||
from .document_version_models import DocumentVersion
|
from .document_models import Document
|
||||||
|
|
||||||
__all__ = ('DocumentPage', 'DocumentPageResult')
|
__all__ = ('DocumentPage', 'DocumentPageResult')
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -35,16 +36,22 @@ 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 +59,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 +68,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 +77,15 @@ 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
|
|
||||||
def document(self):
|
|
||||||
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 +94,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,8 +132,7 @@ 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, 'page_pk': self.pk
|
||||||
'page_pk': self.pk
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
final_url.args['_hash'] = transformations_hash
|
final_url.args['_hash'] = transformations_hash
|
||||||
@@ -190,12 +193,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,14 +219,25 @@ 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.generate_image()
|
||||||
|
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
|
||||||
)
|
)
|
||||||
converter.seek_page(page_number=self.page_number - 1)
|
converter.seek_page(page_number=0)
|
||||||
|
#self.page_number - 1)
|
||||||
|
|
||||||
page_image = converter.get_page()
|
page_image = converter.get_page()
|
||||||
|
|
||||||
|
cache_filename = 'document_page'
|
||||||
|
|
||||||
# Since open "wb+" doesn't create files, create it explicitly
|
# Since open "wb+" doesn't create files, create it explicitly
|
||||||
with self.cache_partition.create_file(filename=cache_filename) as file_object:
|
with self.cache_partition.create_file(filename=cache_filename) as file_object:
|
||||||
file_object.write(page_image.getvalue())
|
file_object.write(page_image.getvalue())
|
||||||
@@ -241,28 +255,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 +296,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 DocumentPageResult(DocumentPage):
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('document_version__document', 'page_number')
|
ordering = ('document', 'page_number')
|
||||||
proxy = True
|
proxy = True
|
||||||
verbose_name = _('Document page')
|
verbose_name = _('Document page result')
|
||||||
verbose_name_plural = _('Document pages')
|
verbose_name_plural = _('Document pages result')
|
||||||
|
|||||||
@@ -15,15 +15,13 @@ 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.exceptions import InvalidOfficeFormat, PageCountError
|
from mayan.apps.converter.exceptions import InvalidOfficeFormat, PageCountError
|
||||||
from mayan.apps.converter.layers import layer_saved_transformations
|
|
||||||
from mayan.apps.converter.transformations import TransformationRotate
|
|
||||||
from mayan.apps.converter.utils import get_converter_class
|
from mayan.apps.converter.utils import get_converter_class
|
||||||
from mayan.apps.mimetype.api import get_mimetype
|
from mayan.apps.mimetype.api import get_mimetype
|
||||||
|
|
||||||
from ..events import event_document_new_version, event_document_version_revert
|
from ..events import event_document_new_version, event_document_version_revert
|
||||||
from ..literals import DOCUMENT_IMAGES_CACHE_NAME
|
from ..literals import DOCUMENT_IMAGES_CACHE_NAME
|
||||||
from ..managers import DocumentVersionManager
|
from ..managers import DocumentVersionManager
|
||||||
from ..settings import setting_fix_orientation, setting_hash_block_size
|
from ..settings import setting_hash_block_size
|
||||||
from ..signals import post_document_created, post_version_upload
|
from ..signals import post_document_created, post_version_upload
|
||||||
from ..storages import storage_documentversion
|
from ..storages import storage_documentversion
|
||||||
|
|
||||||
@@ -152,15 +150,6 @@ class DocumentVersion(models.Model):
|
|||||||
"""
|
"""
|
||||||
return self.file.storage.exists(self.file.name)
|
return self.file.storage.exists(self.file.name)
|
||||||
|
|
||||||
def fix_orientation(self):
|
|
||||||
for page in self.pages.all():
|
|
||||||
degrees = page.detect_orientation()
|
|
||||||
if degrees:
|
|
||||||
layer_saved_transformations.add_to_object(
|
|
||||||
obj=page, transformation=TransformationRotate,
|
|
||||||
arguments='{{"degrees": {}}}'.format(360 - degrees)
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse(
|
return reverse(
|
||||||
viewname='documents:document_version_view', kwargs={
|
viewname='documents:document_version_view', kwargs={
|
||||||
@@ -246,23 +235,12 @@ class DocumentVersion(models.Model):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@property
|
#@property
|
||||||
def pages_all(self):
|
#def page_count(self):
|
||||||
DocumentPage = apps.get_model(
|
# """
|
||||||
app_label='documents', model_name='DocumentPage'
|
# The number of pages that the document posses.
|
||||||
)
|
# """
|
||||||
return DocumentPage.passthrough.filter(document_version=self)
|
# return self.pages.count()
|
||||||
|
|
||||||
@property
|
|
||||||
def pages(self):
|
|
||||||
return self.version_pages.all()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def page_count(self):
|
|
||||||
"""
|
|
||||||
The number of pages that the document posses.
|
|
||||||
"""
|
|
||||||
return self.pages.count()
|
|
||||||
|
|
||||||
def revert(self, _user=None):
|
def revert(self, _user=None):
|
||||||
"""
|
"""
|
||||||
@@ -285,6 +263,7 @@ class DocumentVersion(models.Model):
|
|||||||
Overloaded save method that updates the document version's checksum,
|
Overloaded save method that updates the document version's checksum,
|
||||||
mimetype, and page count when created
|
mimetype, and page count when created
|
||||||
"""
|
"""
|
||||||
|
append_pages = kwargs.pop('append_pages', False)
|
||||||
user = kwargs.pop('_user', None)
|
user = kwargs.pop('_user', None)
|
||||||
new_document_version = not self.pk
|
new_document_version = not self.pk
|
||||||
|
|
||||||
@@ -304,10 +283,8 @@ class DocumentVersion(models.Model):
|
|||||||
# Only do this for new documents
|
# Only do this for new documents
|
||||||
self.update_checksum(save=False)
|
self.update_checksum(save=False)
|
||||||
self.update_mimetype(save=False)
|
self.update_mimetype(save=False)
|
||||||
self.save()
|
self.save(append_pages=append_pages, _user=user)
|
||||||
self.update_page_count(save=False)
|
self.update_page_count(save=False)
|
||||||
if setting_fix_orientation.value:
|
|
||||||
self.fix_orientation()
|
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
'New document version "%s" created for document: %s',
|
'New document version "%s" created for document: %s',
|
||||||
@@ -337,6 +314,14 @@ class DocumentVersion(models.Model):
|
|||||||
sender=Document, instance=self.document
|
sender=Document, instance=self.document
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if append_pages:
|
||||||
|
for version_page in self.pages.all():
|
||||||
|
self.document.pages.create(
|
||||||
|
content_object=version_page
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.document.pages_reset(update_page_count=False)
|
||||||
|
|
||||||
def save_to_file(self, file_object):
|
def save_to_file(self, file_object):
|
||||||
"""
|
"""
|
||||||
Save a copy of the document from the document storage backend
|
Save a copy of the document from the document storage backend
|
||||||
@@ -410,7 +395,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():
|
||||||
|
|||||||
271
mayan/apps/documents/models/document_version_page_models.py
Normal file
271
mayan/apps/documents/models/document_version_page_models.py
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
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_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')
|
||||||
|
)
|
||||||
|
page_number = models.PositiveIntegerField(
|
||||||
|
db_index=True, default=1, editable=False,
|
||||||
|
verbose_name=_('Page number')
|
||||||
|
)
|
||||||
|
|
||||||
|
objects = DocumentVersionPageManager()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_in_trash(self):
|
||||||
|
return self.document_version.document.is_in_trash
|
||||||
|
|
||||||
|
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_pages_reset',
|
||||||
|
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')
|
||||||
|
|||||||
@@ -17,12 +17,20 @@ def transformation_format_uuid(term_string):
|
|||||||
return term_string
|
return term_string
|
||||||
|
|
||||||
|
|
||||||
def get_queryset_page_search_queryset():
|
def get_queryset_document_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='DocumentPage'
|
||||||
)
|
)
|
||||||
return DocumentPage.objects.filter(document_version__document__in_trash=False)
|
return DocumentPage.objects.filter(document__in_trash=False)
|
||||||
|
|
||||||
|
|
||||||
|
def get_queryset_document_version_page_search_queryset():
|
||||||
|
# Ignore documents in trash can
|
||||||
|
DocumentVersionPage = apps.get_model(
|
||||||
|
app_label='documents', model_name='DocumentVersionPage'
|
||||||
|
)
|
||||||
|
return DocumentVersionPage.objects.filter(document_version__document__in_trash=False)
|
||||||
|
|
||||||
|
|
||||||
document_search = SearchModel(
|
document_search = SearchModel(
|
||||||
@@ -30,7 +38,6 @@ document_search = SearchModel(
|
|||||||
model_name='Document', permission=permission_document_view,
|
model_name='Document', permission=permission_document_view,
|
||||||
serializer_path='mayan.apps.documents.serializers.DocumentSerializer'
|
serializer_path='mayan.apps.documents.serializers.DocumentSerializer'
|
||||||
)
|
)
|
||||||
|
|
||||||
document_search.add_model_field(
|
document_search.add_model_field(
|
||||||
field='document_type__label', label=_('Document type')
|
field='document_type__label', label=_('Document type')
|
||||||
)
|
)
|
||||||
@@ -50,24 +57,49 @@ 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='DocumentPage', permission=permission_document_view,
|
||||||
queryset=get_queryset_page_search_queryset,
|
queryset=get_queryset_document_page_search_queryset,
|
||||||
serializer_path='mayan.apps.documents.serializers.DocumentPageSerializer'
|
serializer_path='mayan.apps.documents.serializers.DocumentPageSerializer'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
document_version_page_search = SearchModel(
|
||||||
|
app_label='documents', list_mode=LIST_MODE_CHOICE_ITEM,
|
||||||
|
model_name='DocumentVersionPage', permission=permission_document_view,
|
||||||
|
queryset=get_queryset_document_version_page_search_queryset,
|
||||||
|
serializer_path='mayan.apps.documents.serializers.DocumentVersionPageSerializer'
|
||||||
|
)
|
||||||
|
|
||||||
document_page_search.add_model_field(
|
document_page_search.add_model_field(
|
||||||
field='document_version__document__document_type__label',
|
field='document__document_type__label',
|
||||||
label=_('Document type')
|
label=_('Document type')
|
||||||
)
|
)
|
||||||
document_page_search.add_model_field(
|
document_page_search.add_model_field(
|
||||||
field='document_version__document__versions__mimetype',
|
field='document__versions__mimetype',
|
||||||
label=_('MIME type')
|
label=_('MIME type')
|
||||||
)
|
)
|
||||||
document_page_search.add_model_field(
|
document_page_search.add_model_field(
|
||||||
|
field='document__label', label=_('Label')
|
||||||
|
)
|
||||||
|
document_page_search.add_model_field(
|
||||||
|
field='document__description', label=_('Description')
|
||||||
|
)
|
||||||
|
document_page_search.add_model_field(
|
||||||
|
field='document__versions__checksum', label=_('Checksum')
|
||||||
|
)
|
||||||
|
|
||||||
|
document_version_page_search.add_model_field(
|
||||||
|
field='document_version__document__document_type__label',
|
||||||
|
label=_('Document type')
|
||||||
|
)
|
||||||
|
document_version_page_search.add_model_field(
|
||||||
|
field='document_version__document__versions__mimetype',
|
||||||
|
label=_('MIME type')
|
||||||
|
)
|
||||||
|
document_version_page_search.add_model_field(
|
||||||
field='document_version__document__label', label=_('Label')
|
field='document_version__document__label', label=_('Label')
|
||||||
)
|
)
|
||||||
document_page_search.add_model_field(
|
document_version_page_search.add_model_field(
|
||||||
field='document_version__document__description', label=_('Description')
|
field='document_version__document__description', label=_('Description')
|
||||||
)
|
)
|
||||||
document_page_search.add_model_field(
|
document_version_page_search.add_model_field(
|
||||||
field='document_version__checksum', label=_('Checksum')
|
field='document_version__checksum', label=_('Checksum')
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -38,21 +38,6 @@ setting_documentimagecache_storage_arguments = namespace.add_setting(
|
|||||||
'Arguments to pass to the DOCUMENT_CACHE_STORAGE_BACKEND.'
|
'Arguments to pass to the DOCUMENT_CACHE_STORAGE_BACKEND.'
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
setting_disable_base_image_cache = namespace.add_setting(
|
|
||||||
global_name='DOCUMENTS_DISABLE_BASE_IMAGE_CACHE', default=False,
|
|
||||||
help_text=_(
|
|
||||||
'Disables the first cache tier which stores high resolution, '
|
|
||||||
'non transformed versions of documents\'s pages.'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
setting_disable_transformed_image_cache = namespace.add_setting(
|
|
||||||
global_name='DOCUMENTS_DISABLE_TRANSFORMED_IMAGE_CACHE', default=False,
|
|
||||||
help_text=_(
|
|
||||||
'Disables the second cache tier which stores medium to low '
|
|
||||||
'resolution, transformed (rotated, zoomed, etc) versions '
|
|
||||||
'of documents\' pages.'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
setting_display_height = namespace.add_setting(
|
setting_display_height = namespace.add_setting(
|
||||||
global_name='DOCUMENTS_DISPLAY_HEIGHT', default=''
|
global_name='DOCUMENTS_DISPLAY_HEIGHT', default=''
|
||||||
)
|
)
|
||||||
@@ -65,15 +50,6 @@ setting_favorite_count = namespace.add_setting(
|
|||||||
'Maximum number of favorite documents to remember per user.'
|
'Maximum number of favorite documents to remember per user.'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
setting_fix_orientation = namespace.add_setting(
|
|
||||||
global_name='DOCUMENTS_FIX_ORIENTATION', default=False,
|
|
||||||
help_text=_(
|
|
||||||
'Detect the orientation of each of the document\'s pages '
|
|
||||||
'and create a corresponding rotation transformation to '
|
|
||||||
'display it rightside up. This is an experimental '
|
|
||||||
'feature and it is disabled by default.'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
setting_hash_block_size = namespace.add_setting(
|
setting_hash_block_size = namespace.add_setting(
|
||||||
global_name='DOCUMENTS_HASH_BLOCK_SIZE',
|
global_name='DOCUMENTS_HASH_BLOCK_SIZE',
|
||||||
default=DEFAULT_DOCUMENTS_HASH_BLOCK_SIZE, help_text=_(
|
default=DEFAULT_DOCUMENTS_HASH_BLOCK_SIZE, help_text=_(
|
||||||
|
|||||||
@@ -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_pages_reset(self, document_id):
|
||||||
|
Document = apps.get_model(
|
||||||
|
app_label='documents', model_name='Document'
|
||||||
|
)
|
||||||
|
|
||||||
|
document = Document.objects.get(pk=document_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
document.pages_reset()
|
||||||
|
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(
|
||||||
@@ -122,7 +158,7 @@ def task_update_page_count(self, version_id):
|
|||||||
|
|
||||||
|
|
||||||
@app.task(bind=True, default_retry_delay=UPLOAD_NEW_VERSION_RETRY_DELAY, ignore_result=True)
|
@app.task(bind=True, default_retry_delay=UPLOAD_NEW_VERSION_RETRY_DELAY, ignore_result=True)
|
||||||
def task_upload_new_version(self, document_id, shared_uploaded_file_id, user_id, comment=None):
|
def task_upload_new_version(self, document_id, shared_uploaded_file_id, user_id, append_pages=False, comment=None):
|
||||||
SharedUploadedFile = apps.get_model(
|
SharedUploadedFile = apps.get_model(
|
||||||
app_label='common', model_name='SharedUploadedFile'
|
app_label='common', model_name='SharedUploadedFile'
|
||||||
)
|
)
|
||||||
@@ -157,7 +193,7 @@ def task_upload_new_version(self, document_id, shared_uploaded_file_id, user_id,
|
|||||||
document=document, comment=comment or '', file=file_object
|
document=document, comment=comment or '', file=file_object
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
document_version.save(_user=user)
|
document_version.save(append_pages=append_pages, _user=user)
|
||||||
except Warning as warning:
|
except Warning as warning:
|
||||||
# New document version are blocked
|
# New document version are blocked
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ class DocumentTestMixin(object):
|
|||||||
|
|
||||||
self.test_document = document
|
self.test_document = document
|
||||||
self.test_documents.append(document)
|
self.test_documents.append(document)
|
||||||
|
self.test_document_version = document.latest_version
|
||||||
|
self.test_document_page = document.pages_all.first()
|
||||||
|
|
||||||
|
|
||||||
class DocumentTypeViewTestMixin(object):
|
class DocumentTypeViewTestMixin(object):
|
||||||
@@ -148,6 +150,26 @@ class DocumentVersionTestMixin(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentVersionViewTestMixin(object):
|
||||||
|
def _request_document_version_list_view(self):
|
||||||
|
return self.get(
|
||||||
|
viewname='documents:document_version_list',
|
||||||
|
kwargs={'pk': self.test_document.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_document_version_revert_view(self, document_version):
|
||||||
|
return self.post(
|
||||||
|
viewname='documents:document_version_revert',
|
||||||
|
kwargs={'pk': document_version.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_document_version_page_count_update_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='documents:document_version_page_count_update',
|
||||||
|
kwargs={'pk': self.test_document_version.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DocumentViewTestMixin(object):
|
class DocumentViewTestMixin(object):
|
||||||
def _request_document_properties_view(self):
|
def _request_document_properties_view(self):
|
||||||
return self.get(
|
return self.get(
|
||||||
@@ -200,6 +222,12 @@ class DocumentViewTestMixin(object):
|
|||||||
data={'id_list': self.test_document.pk}
|
data={'id_list': self.test_document.pk}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _request_document_pages_reset_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='documents:document_pages_reset',
|
||||||
|
kwargs={'pk': self.test_document.pk}
|
||||||
|
)
|
||||||
|
|
||||||
def _request_document_version_download(self, data=None):
|
def _request_document_version_download(self, data=None):
|
||||||
data = data or {}
|
data = data or {}
|
||||||
return self.get(
|
return self.get(
|
||||||
@@ -208,18 +236,6 @@ class DocumentViewTestMixin(object):
|
|||||||
}, data=data
|
}, data=data
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_document_update_page_count_view(self):
|
|
||||||
return self.post(
|
|
||||||
viewname='documents:document_update_page_count',
|
|
||||||
kwargs={'pk': self.test_document.pk}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _request_document_multiple_update_page_count_view(self):
|
|
||||||
return self.post(
|
|
||||||
viewname='documents:document_multiple_update_page_count',
|
|
||||||
data={'id_list': self.test_document.pk}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _request_document_clear_transformations_view(self):
|
def _request_document_clear_transformations_view(self):
|
||||||
return self.post(
|
return self.post(
|
||||||
viewname='documents:document_clear_transformations',
|
viewname='documents:document_clear_transformations',
|
||||||
@@ -232,8 +248,11 @@ class DocumentViewTestMixin(object):
|
|||||||
data={'id_list': self.test_document.pk}
|
data={'id_list': self.test_document.pk}
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_empty_trash_view(self):
|
def _request_document_multiple_pages_reset_view(self):
|
||||||
return self.post(viewname='documents:trash_can_empty')
|
return self.post(
|
||||||
|
viewname='documents:document_multiple_pages_reset',
|
||||||
|
data={'id_list': self.test_document.pk}
|
||||||
|
)
|
||||||
|
|
||||||
def _request_document_print_view(self):
|
def _request_document_print_view(self):
|
||||||
return self.get(
|
return self.get(
|
||||||
@@ -243,3 +262,6 @@ class DocumentViewTestMixin(object):
|
|||||||
'page_group': PAGE_RANGE_ALL
|
'page_group': PAGE_RANGE_ALL
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _request_empty_trash_view(self):
|
||||||
|
return self.post(viewname='documents:trash_can_empty')
|
||||||
|
|||||||
@@ -530,8 +530,7 @@ class DocumentPageAPIViewTestMixin(object):
|
|||||||
page = self.test_document.pages.first()
|
page = self.test_document.pages.first()
|
||||||
return self.get(
|
return self.get(
|
||||||
viewname='rest_api:documentpage-image', kwargs={
|
viewname='rest_api:documentpage-image', kwargs={
|
||||||
'pk': page.document.pk, 'version_pk': page.document_version.pk,
|
'pk': page.document.pk, 'page_pk': page.pk
|
||||||
'page_pk': page.pk
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -552,6 +551,33 @@ class DocumentPageAPIViewTestCase(
|
|||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentVersionPageAPIViewTestMixin(object):
|
||||||
|
def _request_document_version_page_image(self):
|
||||||
|
page = self.test_document_version.pages.first()
|
||||||
|
return self.get(
|
||||||
|
viewname='rest_api:documentversionpage-image', kwargs={
|
||||||
|
'pk': page.document.pk, 'version_pk': page.document_version.pk,
|
||||||
|
'page_pk': page.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentVersionPageAPIViewTestCase(
|
||||||
|
DocumentVersionPageAPIViewTestMixin, DocumentTestMixin, BaseAPITestCase
|
||||||
|
):
|
||||||
|
def test_document_version_page_api_image_view_no_access(self):
|
||||||
|
response = self._request_document_version_page_image()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_document_version_page_api_image_view_with_access(self):
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document, permission=permission_document_view
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_document_version_page_image()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class TrashedDocumentAPIViewTestMixin(object):
|
class TrashedDocumentAPIViewTestMixin(object):
|
||||||
def _request_test_document_api_trash_view(self):
|
def _request_test_document_api_trash_view(self):
|
||||||
return self.delete(
|
return self.delete(
|
||||||
@@ -575,13 +601,10 @@ class TrashedDocumentAPIViewTestMixin(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _request_test_trashed_document_api_image_view(self):
|
def _request_test_trashed_document_api_image_view(self):
|
||||||
latest_version = self.test_document.latest_version
|
|
||||||
|
|
||||||
return self.get(
|
return self.get(
|
||||||
viewname='rest_api:documentpage-image', kwargs={
|
viewname='rest_api:documentpage-image', kwargs={
|
||||||
'pk': latest_version.document.pk,
|
'pk': self.test_document.pk,
|
||||||
'version_pk': latest_version.pk,
|
'page_pk': self.test_document.pages.first().pk
|
||||||
'page_pk': latest_version.pages.first().pk
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ from ..permissions import (
|
|||||||
from .base import GenericDocumentViewTestCase
|
from .base import GenericDocumentViewTestCase
|
||||||
|
|
||||||
|
|
||||||
class DocumentPageDisableViewTestCase(GenericDocumentViewTestCase):
|
class DocumentPageDisableViewTestMixin(object):
|
||||||
def setUp(self):
|
def _disable_test_document_page(self):
|
||||||
super(DocumentPageDisableViewTestCase, self).setUp()
|
self.test_document_page.enabled = False
|
||||||
self.test_document_page = self.test_document.pages_all.first()
|
self.test_document_page.save()
|
||||||
|
|
||||||
def _request_test_document_page_disable_view(self):
|
def _request_test_document_page_disable_view(self):
|
||||||
return self.post(
|
return self.post(
|
||||||
@@ -21,6 +21,31 @@ class DocumentPageDisableViewTestCase(GenericDocumentViewTestCase):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _request_test_document_page_enable_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='documents:document_page_enable', kwargs={
|
||||||
|
'pk': self.test_document_page.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_document_page_multiple_disable_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='documents:document_page_multiple_disable', data={
|
||||||
|
'id_list': self.test_document_page.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_document_page_multiple_enable_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='documents:document_page_multiple_enable', data={
|
||||||
|
'id_list': self.test_document_page.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentPageDisableViewTestCase(
|
||||||
|
DocumentPageDisableViewTestMixin, GenericDocumentViewTestCase
|
||||||
|
):
|
||||||
def test_document_page_disable_view_no_permission(self):
|
def test_document_page_disable_view_no_permission(self):
|
||||||
test_document_page_count = self.test_document.pages.count()
|
test_document_page_count = self.test_document.pages.count()
|
||||||
|
|
||||||
@@ -45,13 +70,6 @@ class DocumentPageDisableViewTestCase(GenericDocumentViewTestCase):
|
|||||||
test_document_page_count, self.test_document.pages.count()
|
test_document_page_count, self.test_document.pages.count()
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_test_document_page_multiple_disable_view(self):
|
|
||||||
return self.post(
|
|
||||||
viewname='documents:document_page_multiple_disable', data={
|
|
||||||
'id_list': self.test_document_page.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_document_page_multiple_disable_view_no_permission(self):
|
def test_document_page_multiple_disable_view_no_permission(self):
|
||||||
test_document_page_count = self.test_document.pages.count()
|
test_document_page_count = self.test_document.pages.count()
|
||||||
|
|
||||||
@@ -76,17 +94,6 @@ class DocumentPageDisableViewTestCase(GenericDocumentViewTestCase):
|
|||||||
test_document_page_count, self.test_document.pages.count()
|
test_document_page_count, self.test_document.pages.count()
|
||||||
)
|
)
|
||||||
|
|
||||||
def _disable_test_document_page(self):
|
|
||||||
self.test_document_page.enabled = False
|
|
||||||
self.test_document_page.save()
|
|
||||||
|
|
||||||
def _request_test_document_page_enable_view(self):
|
|
||||||
return self.post(
|
|
||||||
viewname='documents:document_page_enable', kwargs={
|
|
||||||
'pk': self.test_document_page.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_document_page_enable_view_no_permission(self):
|
def test_document_page_enable_view_no_permission(self):
|
||||||
self._disable_test_document_page()
|
self._disable_test_document_page()
|
||||||
|
|
||||||
@@ -114,13 +121,6 @@ class DocumentPageDisableViewTestCase(GenericDocumentViewTestCase):
|
|||||||
test_document_page_count, self.test_document.pages.count()
|
test_document_page_count, self.test_document.pages.count()
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_test_document_page_multiple_enable_view(self):
|
|
||||||
return self.post(
|
|
||||||
viewname='documents:document_page_multiple_enable', data={
|
|
||||||
'id_list': self.test_document_page.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_document_page_multiple_enable_view_no_permission(self):
|
def test_document_page_multiple_enable_view_no_permission(self):
|
||||||
self._disable_test_document_page()
|
self._disable_test_document_page()
|
||||||
test_document_page_count = self.test_document.pages.count()
|
test_document_page_count = self.test_document.pages.count()
|
||||||
@@ -148,7 +148,7 @@ class DocumentPageDisableViewTestCase(GenericDocumentViewTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DocumentPageViewTestCase(GenericDocumentViewTestCase):
|
class DocumentPageViewTestMixin(object):
|
||||||
def _request_test_document_page_list_view(self):
|
def _request_test_document_page_list_view(self):
|
||||||
return self.get(
|
return self.get(
|
||||||
viewname='documents:document_pages', kwargs={
|
viewname='documents:document_pages', kwargs={
|
||||||
@@ -156,6 +156,18 @@ class DocumentPageViewTestCase(GenericDocumentViewTestCase):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _request_test_document_page_view(self, document_page):
|
||||||
|
return self.get(
|
||||||
|
viewname='documents:document_page_view', kwargs={
|
||||||
|
'pk': document_page.pk,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentPageViewTestCase(
|
||||||
|
DocumentPageViewTestMixin, GenericDocumentViewTestCase
|
||||||
|
):
|
||||||
|
|
||||||
def test_document_page_list_view_no_permission(self):
|
def test_document_page_list_view_no_permission(self):
|
||||||
response = self._request_test_document_page_list_view()
|
response = self._request_test_document_page_list_view()
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
@@ -170,13 +182,6 @@ class DocumentPageViewTestCase(GenericDocumentViewTestCase):
|
|||||||
response=response, text=self.test_document.label, status_code=200
|
response=response, text=self.test_document.label, status_code=200
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_test_document_page_view(self, document_page):
|
|
||||||
return self.get(
|
|
||||||
viewname='documents:document_page_view', kwargs={
|
|
||||||
'pk': document_page.pk,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_document_page_view_no_permissions(self):
|
def test_document_page_view_no_permissions(self):
|
||||||
response = self._request_test_document_page_view(
|
response = self._request_test_document_page_view(
|
||||||
document_page=self.test_document.pages.first()
|
document_page=self.test_document.pages.first()
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from ..permissions import (
|
from ..permissions import (
|
||||||
permission_document_version_revert, permission_document_version_view,
|
permission_document_tools, permission_document_version_revert,
|
||||||
|
permission_document_version_view,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .base import GenericDocumentViewTestCase
|
from .base import GenericDocumentViewTestCase
|
||||||
from .literals import TEST_VERSION_COMMENT
|
from .literals import TEST_VERSION_COMMENT
|
||||||
from .mixins import DocumentVersionTestMixin
|
from .mixins import DocumentVersionTestMixin, DocumentVersionViewTestMixin
|
||||||
|
|
||||||
|
|
||||||
class DocumentVersionTestCase(DocumentVersionTestMixin, GenericDocumentViewTestCase):
|
class DocumentVersionTestCase(
|
||||||
def _request_document_version_list_view(self):
|
DocumentVersionViewTestMixin, DocumentVersionTestMixin,
|
||||||
return self.get(
|
GenericDocumentViewTestCase
|
||||||
viewname='documents:document_version_list',
|
):
|
||||||
kwargs={'pk': self.test_document.pk}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_document_version_list_no_permission(self):
|
def test_document_version_list_no_permission(self):
|
||||||
self._upload_new_version()
|
self._upload_new_version()
|
||||||
|
|
||||||
@@ -33,12 +31,6 @@ class DocumentVersionTestCase(DocumentVersionTestMixin, GenericDocumentViewTestC
|
|||||||
response=response, text=TEST_VERSION_COMMENT, status_code=200
|
response=response, text=TEST_VERSION_COMMENT, status_code=200
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_document_version_revert_view(self, document_version):
|
|
||||||
return self.post(
|
|
||||||
viewname='documents:document_version_revert',
|
|
||||||
kwargs={'pk': document_version.pk}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_document_version_revert_no_permission(self):
|
def test_document_version_revert_no_permission(self):
|
||||||
first_version = self.test_document.latest_version
|
first_version = self.test_document.latest_version
|
||||||
self._upload_new_version()
|
self._upload_new_version()
|
||||||
@@ -64,3 +56,25 @@ class DocumentVersionTestCase(DocumentVersionTestMixin, GenericDocumentViewTestC
|
|||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
self.assertEqual(self.test_document.versions.count(), 1)
|
self.assertEqual(self.test_document.versions.count(), 1)
|
||||||
|
|
||||||
|
def test_document_version_page_count_update_view_no_permission(self):
|
||||||
|
self.test_document_version.pages.all().delete()
|
||||||
|
|
||||||
|
response = self._request_test_document_version_page_count_update_view()
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
self.assertEqual(self.test_document_version.pages.count(), 0)
|
||||||
|
|
||||||
|
def test_document_version_page_count_update_view_with_access(self):
|
||||||
|
page_count = self.test_document_version.pages.count()
|
||||||
|
|
||||||
|
self.test_document_version.pages.all().delete()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document, permission=permission_document_tools
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_version_page_count_update_view()
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
self.assertEqual(self.test_document_version.pages.count(), page_count)
|
||||||
|
|||||||
@@ -292,46 +292,44 @@ class DocumentsViewsTestCase(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_document_update_page_count_view_no_permission(self):
|
def test_document_pages_reset_view_no_permission(self):
|
||||||
self.test_document.pages.all().delete()
|
self.test_document.pages.all().delete()
|
||||||
self.assertEqual(self.test_document.pages.count(), 0)
|
|
||||||
|
|
||||||
response = self._request_document_update_page_count_view()
|
response = self._request_document_pages_reset_view()
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
self.assertEqual(self.test_document.pages.count(), 0)
|
self.assertEqual(self.test_document.pages.count(), 0)
|
||||||
|
|
||||||
def test_document_update_page_count_view_with_permission(self):
|
def test_document_pages_reset_view_with_access(self):
|
||||||
# TODO: Revise permission association
|
|
||||||
|
|
||||||
page_count = self.test_document.pages.count()
|
page_count = self.test_document.pages.count()
|
||||||
self.test_document.pages.all().delete()
|
self.test_document.pages.all().delete()
|
||||||
self.assertEqual(self.test_document.pages.count(), 0)
|
|
||||||
|
|
||||||
self.grant_permission(permission=permission_document_tools)
|
self.grant_access(
|
||||||
|
obj=self.test_document, permission=permission_document_tools
|
||||||
|
)
|
||||||
|
|
||||||
response = self._request_document_update_page_count_view()
|
response = self._request_document_pages_reset_view()
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
self.assertEqual(self.test_document.pages.count(), page_count)
|
self.assertEqual(self.test_document.pages.count(), page_count)
|
||||||
|
|
||||||
def test_document_multiple_update_page_count_view_no_permission(self):
|
def test_document_multiple_pages_reset_view_no_permission(self):
|
||||||
self.test_document.pages.all().delete()
|
self.test_document.pages.all().delete()
|
||||||
self.assertEqual(self.test_document.pages.count(), 0)
|
|
||||||
|
|
||||||
response = self._request_document_multiple_update_page_count_view()
|
response = self._request_document_multiple_pages_reset_view()
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
self.assertEqual(self.test_document.pages.count(), 0)
|
self.assertEqual(self.test_document.pages.count(), 0)
|
||||||
|
|
||||||
def test_document_multiple_update_page_count_view_with_permission(self):
|
def test_document_multiple_pages_reset_view_with_access(self):
|
||||||
page_count = self.test_document.pages.count()
|
page_count = self.test_document.pages.count()
|
||||||
self.test_document.pages.all().delete()
|
self.test_document.pages.all().delete()
|
||||||
self.assertEqual(self.test_document.pages.count(), 0)
|
|
||||||
|
|
||||||
self.grant_permission(permission=permission_document_tools)
|
self.grant_access(
|
||||||
|
obj=self.test_document, permission=permission_document_tools
|
||||||
|
)
|
||||||
|
|
||||||
response = self._request_document_multiple_update_page_count_view()
|
response = self._request_document_multiple_pages_reset_view()
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
self.assertEqual(self.test_document.pages.count(), page_count)
|
self.assertEqual(self.test_document.pages.count(), page_count)
|
||||||
|
|||||||
@@ -1,22 +1,30 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from mayan.apps.common.tests.base import BaseTestCase
|
from mayan.apps.common.tests.base import BaseTestCase
|
||||||
from mayan.apps.documents.permissions import permission_document_view
|
|
||||||
from mayan.apps.documents.search import document_search, document_page_search
|
from ..permissions import permission_document_view
|
||||||
from mayan.apps.documents.tests.mixins import DocumentTestMixin
|
from ..search import document_search, document_page_search
|
||||||
|
|
||||||
|
from .mixins import DocumentTestMixin
|
||||||
|
|
||||||
|
|
||||||
class DocumentSearchTestCase(DocumentTestMixin, BaseTestCase):
|
class DocumentSearchTestMixin(object):
|
||||||
def _perform_document_page_search(self):
|
def _perform_document_page_search(self):
|
||||||
return document_page_search.search(
|
return document_page_search.search(
|
||||||
query_string={'q': self.test_document.label}, user=self._test_case_user
|
query_string={'q': self.test_document.label},
|
||||||
|
user=self._test_case_user
|
||||||
)
|
)
|
||||||
|
|
||||||
def _perform_document_search(self):
|
def _perform_document_search(self):
|
||||||
return document_search.search(
|
return document_search.search(
|
||||||
query_string={'q': self.test_document.label}, user=self._test_case_user
|
query_string={'q': self.test_document.label},
|
||||||
|
user=self._test_case_user
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentSearchTestCase(
|
||||||
|
DocumentSearchTestMixin, DocumentTestMixin, BaseTestCase
|
||||||
|
):
|
||||||
def test_document_page_search_no_access(self):
|
def test_document_page_search_no_access(self):
|
||||||
queryset = self._perform_document_page_search()
|
queryset = self._perform_document_page_search()
|
||||||
self.assertFalse(self.test_document.pages.first() in queryset)
|
self.assertFalse(self.test_document.pages.first() in queryset)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from ..permissions import (
|
|||||||
from .base import GenericDocumentViewTestCase
|
from .base import GenericDocumentViewTestCase
|
||||||
|
|
||||||
|
|
||||||
class TrashedDocumentTestCase(GenericDocumentViewTestCase):
|
class TrashedDocumentTestMixin(object):
|
||||||
def _request_document_restore_get_view(self):
|
def _request_document_restore_get_view(self):
|
||||||
return self.get(
|
return self.get(
|
||||||
viewname='documents:document_restore', kwargs={
|
viewname='documents:document_restore', kwargs={
|
||||||
@@ -17,6 +17,48 @@ class TrashedDocumentTestCase(GenericDocumentViewTestCase):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _request_document_restore_post_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='documents:document_restore', kwargs={
|
||||||
|
'pk': self.test_document.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_document_trash_get_view(self):
|
||||||
|
return self.get(
|
||||||
|
viewname='documents:document_trash', kwargs={
|
||||||
|
'pk': self.test_document.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_document_trash_post_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='documents:document_trash', kwargs={
|
||||||
|
'pk': self.test_document.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_trashed_document_delete_get_view(self):
|
||||||
|
return self.get(
|
||||||
|
viewname='documents:document_delete', kwargs={
|
||||||
|
'pk': self.test_document.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_trashed_document_delete_post_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='documents:document_delete', kwargs={
|
||||||
|
'pk': self.test_document.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_trashed_document_list_view(self):
|
||||||
|
return self.get(viewname='documents:document_list_deleted')
|
||||||
|
|
||||||
|
|
||||||
|
class TrashedDocumentTestCase(
|
||||||
|
TrashedDocumentTestMixin, GenericDocumentViewTestCase
|
||||||
|
):
|
||||||
def test_document_restore_get_view_no_permission(self):
|
def test_document_restore_get_view_no_permission(self):
|
||||||
self.test_document.delete()
|
self.test_document.delete()
|
||||||
self.assertEqual(Document.objects.count(), 0)
|
self.assertEqual(Document.objects.count(), 0)
|
||||||
@@ -43,13 +85,6 @@ class TrashedDocumentTestCase(GenericDocumentViewTestCase):
|
|||||||
|
|
||||||
self.assertEqual(Document.objects.count(), document_count)
|
self.assertEqual(Document.objects.count(), document_count)
|
||||||
|
|
||||||
def _request_document_restore_post_view(self):
|
|
||||||
return self.post(
|
|
||||||
viewname='documents:document_restore', kwargs={
|
|
||||||
'pk': self.test_document.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_document_restore_post_view_no_permission(self):
|
def test_document_restore_post_view_no_permission(self):
|
||||||
self.test_document.delete()
|
self.test_document.delete()
|
||||||
self.assertEqual(Document.objects.count(), 0)
|
self.assertEqual(Document.objects.count(), 0)
|
||||||
@@ -74,13 +109,6 @@ class TrashedDocumentTestCase(GenericDocumentViewTestCase):
|
|||||||
self.assertEqual(DeletedDocument.objects.count(), 0)
|
self.assertEqual(DeletedDocument.objects.count(), 0)
|
||||||
self.assertEqual(Document.objects.count(), 1)
|
self.assertEqual(Document.objects.count(), 1)
|
||||||
|
|
||||||
def _request_document_trash_get_view(self):
|
|
||||||
return self.get(
|
|
||||||
viewname='documents:document_trash', kwargs={
|
|
||||||
'pk': self.test_document.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_document_trash_get_view_no_permissions(self):
|
def test_document_trash_get_view_no_permissions(self):
|
||||||
document_count = Document.objects.count()
|
document_count = Document.objects.count()
|
||||||
|
|
||||||
@@ -101,13 +129,6 @@ class TrashedDocumentTestCase(GenericDocumentViewTestCase):
|
|||||||
|
|
||||||
self.assertEqual(Document.objects.count(), document_count)
|
self.assertEqual(Document.objects.count(), document_count)
|
||||||
|
|
||||||
def _request_document_trash_post_view(self):
|
|
||||||
return self.post(
|
|
||||||
viewname='documents:document_trash', kwargs={
|
|
||||||
'pk': self.test_document.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_document_trash_post_view_no_permissions(self):
|
def test_document_trash_post_view_no_permissions(self):
|
||||||
response = self._request_document_trash_post_view()
|
response = self._request_document_trash_post_view()
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
@@ -126,13 +147,6 @@ class TrashedDocumentTestCase(GenericDocumentViewTestCase):
|
|||||||
self.assertEqual(DeletedDocument.objects.count(), 1)
|
self.assertEqual(DeletedDocument.objects.count(), 1)
|
||||||
self.assertEqual(Document.objects.count(), 0)
|
self.assertEqual(Document.objects.count(), 0)
|
||||||
|
|
||||||
def _request_document_delete_get_view(self):
|
|
||||||
return self.get(
|
|
||||||
viewname='documents:document_delete', kwargs={
|
|
||||||
'pk': self.test_document.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_document_delete_get_view_no_permissions(self):
|
def test_document_delete_get_view_no_permissions(self):
|
||||||
self.test_document.delete()
|
self.test_document.delete()
|
||||||
self.assertEqual(Document.objects.count(), 0)
|
self.assertEqual(Document.objects.count(), 0)
|
||||||
@@ -140,7 +154,7 @@ class TrashedDocumentTestCase(GenericDocumentViewTestCase):
|
|||||||
|
|
||||||
trashed_document_count = DeletedDocument.objects.count()
|
trashed_document_count = DeletedDocument.objects.count()
|
||||||
|
|
||||||
response = self._request_document_delete_get_view()
|
response = self._request_trashed_document_delete_get_view()
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -158,26 +172,19 @@ class TrashedDocumentTestCase(GenericDocumentViewTestCase):
|
|||||||
|
|
||||||
trashed_document_count = DeletedDocument.objects.count()
|
trashed_document_count = DeletedDocument.objects.count()
|
||||||
|
|
||||||
response = self._request_document_delete_get_view()
|
response = self._request_trashed_document_delete_get_view()
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
DeletedDocument.objects.count(), trashed_document_count
|
DeletedDocument.objects.count(), trashed_document_count
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_document_delete_post_view(self):
|
|
||||||
return self.post(
|
|
||||||
viewname='documents:document_delete', kwargs={
|
|
||||||
'pk': self.test_document.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_document_delete_post_view_no_permissions(self):
|
def test_document_delete_post_view_no_permissions(self):
|
||||||
self.test_document.delete()
|
self.test_document.delete()
|
||||||
self.assertEqual(Document.objects.count(), 0)
|
self.assertEqual(Document.objects.count(), 0)
|
||||||
self.assertEqual(DeletedDocument.objects.count(), 1)
|
self.assertEqual(DeletedDocument.objects.count(), 1)
|
||||||
|
|
||||||
response = self._request_document_delete_post_view()
|
response = self._request_trashed_document_delete_post_view()
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
self.assertEqual(Document.objects.count(), 0)
|
self.assertEqual(Document.objects.count(), 0)
|
||||||
@@ -192,19 +199,16 @@ class TrashedDocumentTestCase(GenericDocumentViewTestCase):
|
|||||||
obj=self.test_document, permission=permission_document_delete
|
obj=self.test_document, permission=permission_document_delete
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self._request_document_delete_post_view()
|
response = self._request_trashed_document_delete_post_view()
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
self.assertEqual(DeletedDocument.objects.count(), 0)
|
self.assertEqual(DeletedDocument.objects.count(), 0)
|
||||||
self.assertEqual(Document.objects.count(), 0)
|
self.assertEqual(Document.objects.count(), 0)
|
||||||
|
|
||||||
def _request_document_list_deleted_view(self):
|
|
||||||
return self.get(viewname='documents:document_list_deleted')
|
|
||||||
|
|
||||||
def test_deleted_document_list_view_no_permissions(self):
|
def test_deleted_document_list_view_no_permissions(self):
|
||||||
self.test_document.delete()
|
self.test_document.delete()
|
||||||
|
|
||||||
response = self._request_document_list_deleted_view()
|
response = self._request_trashed_document_list_view()
|
||||||
self.assertNotContains(
|
self.assertNotContains(
|
||||||
response=response, text=self.test_document.label, status_code=200
|
response=response, text=self.test_document.label, status_code=200
|
||||||
)
|
)
|
||||||
@@ -216,7 +220,7 @@ class TrashedDocumentTestCase(GenericDocumentViewTestCase):
|
|||||||
obj=self.test_document, permission=permission_document_view
|
obj=self.test_document, permission=permission_document_view
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self._request_document_list_deleted_view()
|
response = self._request_trashed_document_list_view()
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response=response, text=self.test_document.label, status_code=200
|
response=response, text=self.test_document.label, status_code=200
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,21 +4,24 @@ 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,
|
||||||
DocumentDownloadView, DocumentDuplicatesListView, DocumentEditView,
|
DocumentDownloadView, DocumentDuplicatesListView, DocumentEditView,
|
||||||
DocumentListView, DocumentPreviewView, DocumentPrint,
|
DocumentListView, DocumentPreviewView, DocumentPrint,
|
||||||
DocumentTransformationsClearView, DocumentTransformationsCloneView,
|
DocumentPagesResetView, DocumentTransformationsClearView,
|
||||||
DocumentUpdatePageCountView, DocumentView, DuplicatedDocumentListView,
|
DocumentTransformationsCloneView, DocumentView,
|
||||||
RecentAccessDocumentListView, RecentAddedDocumentListView
|
DuplicatedDocumentListView, RecentAccessDocumentListView,
|
||||||
|
RecentAddedDocumentListView
|
||||||
)
|
)
|
||||||
from .views.document_page_views import (
|
from .views.document_page_views import (
|
||||||
DocumentPageDisable, DocumentPageEnable, DocumentPageListView,
|
DocumentPageDisable, DocumentPageEnable, DocumentPageListView,
|
||||||
@@ -30,7 +33,8 @@ from .views.document_page_views import (
|
|||||||
)
|
)
|
||||||
from .views.document_version_views import (
|
from .views.document_version_views import (
|
||||||
DocumentVersionDownloadFormView, DocumentVersionDownloadView,
|
DocumentVersionDownloadFormView, DocumentVersionDownloadView,
|
||||||
DocumentVersionListView, DocumentVersionRevertView, DocumentVersionView,
|
DocumentVersionListView, DocumentVersionRevertView,
|
||||||
|
DocumentVersionUpdatePageCountView, DocumentVersionView,
|
||||||
)
|
)
|
||||||
from .views.document_type_views import (
|
from .views.document_type_views import (
|
||||||
DocumentTypeCreateView, DocumentTypeDeleteView,
|
DocumentTypeCreateView, DocumentTypeDeleteView,
|
||||||
@@ -172,14 +176,14 @@ urlpatterns_documents = [
|
|||||||
name='document_print'
|
name='document_print'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^documents/(?P<pk>\d+)/reset_page_count/$',
|
regex=r'^documents/(?P<pk>\d+)/pages/reset/$',
|
||||||
view=DocumentUpdatePageCountView.as_view(),
|
view=DocumentPagesResetView.as_view(),
|
||||||
name='document_update_page_count'
|
name='document_pages_reset'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^documents/multiple/reset_page_count/$',
|
regex=r'^documents/multiple/pages/reset/$',
|
||||||
view=DocumentUpdatePageCountView.as_view(),
|
view=DocumentPagesResetView.as_view(),
|
||||||
name='document_multiple_update_page_count'
|
name='document_multiple_pages_reset'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^documents/(?P<pk>\d+)/download/form/$',
|
regex=r'^documents/(?P<pk>\d+)/download/form/$',
|
||||||
@@ -305,6 +309,16 @@ urlpatterns_document_versions = [
|
|||||||
view=DocumentVersionDownloadView.as_view(),
|
view=DocumentVersionDownloadView.as_view(),
|
||||||
name='document_version_download'
|
name='document_version_download'
|
||||||
),
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^documents/versions/(?P<pk>\d+)/pages/update/$',
|
||||||
|
view=DocumentVersionUpdatePageCountView.as_view(),
|
||||||
|
name='document_version_page_count_update'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^documents/versions/multiple/pages/update/$',
|
||||||
|
view=DocumentVersionUpdatePageCountView.as_view(),
|
||||||
|
name='document_version_multiple_page_count_update'
|
||||||
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^documents/versions/(?P<pk>\d+)/revert/$',
|
regex=r'^documents/versions/(?P<pk>\d+)/revert/$',
|
||||||
view=DocumentVersionRevertView.as_view(),
|
view=DocumentVersionRevertView.as_view(),
|
||||||
@@ -405,6 +419,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 +435,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'
|
||||||
|
|||||||
@@ -20,7 +20,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_pages_reset
|
||||||
from ..models import Document, DocumentPage
|
from ..models import Document, DocumentPage
|
||||||
from ..permissions import permission_document_edit, permission_document_view
|
from ..permissions import permission_document_edit, permission_document_view
|
||||||
from ..settings import (
|
from ..settings import (
|
||||||
@@ -50,13 +50,13 @@ class DocumentPageListView(ExternalObjectMixin, SingleObjectListView):
|
|||||||
'hide_object': True,
|
'hide_object': True,
|
||||||
'list_as_items': True,
|
'list_as_items': True,
|
||||||
'no_results_icon': icon_document_pages,
|
'no_results_icon': icon_document_pages,
|
||||||
'no_results_main_link': link_document_update_page_count.resolve(
|
'no_results_main_link': link_document_pages_reset.resolve(
|
||||||
request=self.request, resolved_object=self.external_object
|
request=self.request, resolved_object=self.external_object
|
||||||
),
|
),
|
||||||
'no_results_text': _(
|
'no_results_text': _(
|
||||||
'This could mean that the document is of a format that is '
|
'This could mean that the document is of a format that is '
|
||||||
'not supported, that it is corrupted or that the upload '
|
'not supported, that it is corrupted, or that the upload '
|
||||||
'process was interrupted. Use the document page recalculation '
|
'process was interrupted. Use the document page reset '
|
||||||
'action to attempt to introspect the page count again.'
|
'action to attempt to introspect the page count again.'
|
||||||
),
|
),
|
||||||
'no_results_title': _('No document pages available'),
|
'no_results_title': _('No document pages available'),
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ from __future__ import absolute_import, unicode_literals
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _, ungettext
|
||||||
|
|
||||||
from mayan.apps.common.generics import (
|
from mayan.apps.common.generics import (
|
||||||
ConfirmView, SingleObjectDetailView, SingleObjectListView
|
ConfirmView, MultipleObjectConfirmActionView, SingleObjectDetailView,
|
||||||
|
SingleObjectListView
|
||||||
)
|
)
|
||||||
from mayan.apps.common.mixins import ExternalObjectMixin
|
from mayan.apps.common.mixins import ExternalObjectMixin
|
||||||
|
|
||||||
@@ -14,9 +15,10 @@ from ..events import event_document_view
|
|||||||
from ..forms import DocumentVersionDownloadForm, DocumentVersionPreviewForm
|
from ..forms import DocumentVersionDownloadForm, DocumentVersionPreviewForm
|
||||||
from ..models import Document, DocumentVersion
|
from ..models import Document, DocumentVersion
|
||||||
from ..permissions import (
|
from ..permissions import (
|
||||||
permission_document_download, permission_document_version_revert,
|
permission_document_download, permission_document_tools,
|
||||||
permission_document_version_view
|
permission_document_version_revert, permission_document_version_view
|
||||||
)
|
)
|
||||||
|
from ..tasks import task_update_page_count
|
||||||
|
|
||||||
from .document_views import DocumentDownloadFormView, DocumentDownloadView
|
from .document_views import DocumentDownloadFormView, DocumentDownloadView
|
||||||
|
|
||||||
@@ -142,6 +144,45 @@ class DocumentVersionRevertView(ExternalObjectMixin, ConfirmView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentVersionUpdatePageCountView(MultipleObjectConfirmActionView):
|
||||||
|
model = DocumentVersion
|
||||||
|
object_permission = permission_document_tools
|
||||||
|
success_message = _(
|
||||||
|
'%(count)d document version queued for page count recalculation'
|
||||||
|
)
|
||||||
|
success_message_plural = _(
|
||||||
|
'%(count)d documents version queued for page count recalculation'
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
queryset = self.object_list
|
||||||
|
|
||||||
|
result = {
|
||||||
|
'title': ungettext(
|
||||||
|
singular='Recalculate the page count of the selected document version?',
|
||||||
|
plural='Recalculate the page count of the selected document versions?',
|
||||||
|
number=queryset.count()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if queryset.count() == 1:
|
||||||
|
result.update(
|
||||||
|
{
|
||||||
|
'object': queryset.first(),
|
||||||
|
'title': _(
|
||||||
|
'Recalculate the page count of the document version: %s?'
|
||||||
|
) % queryset.first()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def object_action(self, form, instance):
|
||||||
|
task_update_page_count.apply_async(
|
||||||
|
kwargs={'version_id': instance.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DocumentVersionView(SingleObjectDetailView):
|
class DocumentVersionView(SingleObjectDetailView):
|
||||||
form_class = DocumentVersionPreviewForm
|
form_class = DocumentVersionPreviewForm
|
||||||
model = DocumentVersion
|
model = DocumentVersion
|
||||||
|
|||||||
@@ -44,14 +44,14 @@ from ..permissions import (
|
|||||||
from ..settings import (
|
from ..settings import (
|
||||||
setting_print_width, setting_print_height, setting_recent_added_count
|
setting_print_width, setting_print_height, setting_recent_added_count
|
||||||
)
|
)
|
||||||
from ..tasks import task_update_page_count
|
from ..tasks import task_document_pages_reset
|
||||||
from ..utils import parse_range
|
from ..utils import parse_range
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'DocumentListView', 'DocumentDocumentTypeEditView',
|
'DocumentListView', 'DocumentDocumentTypeEditView',
|
||||||
'DocumentDuplicatesListView', 'DocumentEditView', 'DocumentPreviewView',
|
'DocumentDuplicatesListView', 'DocumentEditView', 'DocumentPreviewView',
|
||||||
'DocumentView', 'DocumentDownloadFormView', 'DocumentDownloadView',
|
'DocumentView', 'DocumentDownloadFormView', 'DocumentDownloadView',
|
||||||
'DocumentUpdatePageCountView', 'DocumentTransformationsClearView',
|
'DocumentPagesResetView', 'DocumentTransformationsClearView',
|
||||||
'DocumentTransformationsCloneView', 'DocumentPrint',
|
'DocumentTransformationsCloneView', 'DocumentPrint',
|
||||||
'DuplicatedDocumentListView', 'RecentAccessDocumentListView',
|
'DuplicatedDocumentListView', 'RecentAccessDocumentListView',
|
||||||
'RecentAddedDocumentListView'
|
'RecentAddedDocumentListView'
|
||||||
@@ -418,6 +418,52 @@ class DocumentPreviewView(SingleObjectDetailView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentPagesResetView(MultipleObjectConfirmActionView):
|
||||||
|
model = Document
|
||||||
|
object_permission = permission_document_tools
|
||||||
|
success_message = _('%(count)d document queued for pages reset')
|
||||||
|
success_message_plural = _('%(count)d documents queued for pages reset')
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
queryset = self.object_list
|
||||||
|
|
||||||
|
result = {
|
||||||
|
'title': ungettext(
|
||||||
|
singular='Reset the pages of the selected document?',
|
||||||
|
plural='Reset the pages of the selected documents?',
|
||||||
|
number=queryset.count()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if queryset.count() == 1:
|
||||||
|
result.update(
|
||||||
|
{
|
||||||
|
'object': queryset.first(),
|
||||||
|
'title': _(
|
||||||
|
'Reset the pages of the document: %s?'
|
||||||
|
) % queryset.first()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def object_action(self, form, instance):
|
||||||
|
latest_version = instance.latest_version
|
||||||
|
if latest_version:
|
||||||
|
task_document_pages_reset.apply_async(
|
||||||
|
kwargs={'document_id': instance.pk}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
messages.error(
|
||||||
|
self.request, _(
|
||||||
|
'Document "%(document)s" is empty. Upload at least one '
|
||||||
|
'document version before attempting to reset the pages. '
|
||||||
|
) % {
|
||||||
|
'document': instance,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DocumentView(SingleObjectDetailView):
|
class DocumentView(SingleObjectDetailView):
|
||||||
form_class = DocumentPropertiesForm
|
form_class = DocumentPropertiesForm
|
||||||
model = Document
|
model = Document
|
||||||
@@ -436,57 +482,6 @@ class DocumentView(SingleObjectDetailView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class DocumentUpdatePageCountView(MultipleObjectConfirmActionView):
|
|
||||||
model = Document
|
|
||||||
object_permission = permission_document_tools
|
|
||||||
success_message = _(
|
|
||||||
'%(count)d document queued for page count recalculation'
|
|
||||||
)
|
|
||||||
success_message_plural = _(
|
|
||||||
'%(count)d documents queued for page count recalculation'
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_extra_context(self):
|
|
||||||
queryset = self.object_list
|
|
||||||
|
|
||||||
result = {
|
|
||||||
'title': ungettext(
|
|
||||||
singular='Recalculate the page count of the selected document?',
|
|
||||||
plural='Recalculate the page count of the selected documents?',
|
|
||||||
number=queryset.count()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if queryset.count() == 1:
|
|
||||||
result.update(
|
|
||||||
{
|
|
||||||
'object': queryset.first(),
|
|
||||||
'title': _(
|
|
||||||
'Recalculate the page count of the document: %s?'
|
|
||||||
) % queryset.first()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def object_action(self, form, instance):
|
|
||||||
latest_version = instance.latest_version
|
|
||||||
if latest_version:
|
|
||||||
task_update_page_count.apply_async(
|
|
||||||
kwargs={'version_id': latest_version.pk}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
messages.error(
|
|
||||||
self.request, _(
|
|
||||||
'Document "%(document)s" is empty. Upload at least one '
|
|
||||||
'document version before attempting to detect the '
|
|
||||||
'page count.'
|
|
||||||
) % {
|
|
||||||
'document': instance,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DocumentTransformationsClearView(MultipleObjectConfirmActionView):
|
class DocumentTransformationsClearView(MultipleObjectConfirmActionView):
|
||||||
model = Document
|
model = Document
|
||||||
object_permission = permission_transformation_delete
|
object_permission = permission_transformation_delete
|
||||||
|
|||||||
@@ -184,7 +184,14 @@ class SearchModel(object):
|
|||||||
query_string=query_string, global_and_search=global_and_search
|
query_string=query_string, global_and_search=global_and_search
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
queryset = self.get_queryset().filter(search_query.query).distinct()
|
queryset = self.get_queryset().filter(search_query.query).distinct()
|
||||||
|
except Exception:
|
||||||
|
logger.error(
|
||||||
|
'Error filtering model %s with queryset: %s', self.model,
|
||||||
|
search_query.query
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
if self.permission:
|
if self.permission:
|
||||||
queryset = AccessControlList.objects.restrict_queryset(
|
queryset = AccessControlList.objects.restrict_queryset(
|
||||||
|
|||||||
17
mayan/apps/dynamic_search/settings.py
Normal file
17
mayan/apps/dynamic_search/settings.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
|
||||||
|
namespace = Namespace(label=_('Search'), name='search')
|
||||||
|
|
||||||
|
setting_disable_simple_search = namespace.add_setting(
|
||||||
|
global_name='SEARCH_DISABLE_SIMPLE_SEARCH',
|
||||||
|
default=False, help_text=_(
|
||||||
|
'Disables the single term bar search leaving only the advanced '
|
||||||
|
'search button.'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@@ -1,13 +1,25 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% load search_tags %}
|
{% load search_tags %}
|
||||||
|
{% load smart_settings_tags %}
|
||||||
|
|
||||||
{% get_search_models as search_models %}
|
{% get_search_models as search_models %}
|
||||||
|
{% smart_setting global_name="SEARCH_DISABLE_SIMPLE_SEARCH" as setting_disable_simple_search %}
|
||||||
|
|
||||||
|
{% if setting_disable_simple_search %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-6 col-xs-offset-3">
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="well center-block">
|
<div class="well center-block">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<form action="{% url 'search:results' %}" class="form-horizontal" id="formSearch" method="get" role="search">
|
<form action="{% url 'search:results' %}" class="form-horizontal" id="formSearch" method="get" role="search">
|
||||||
|
{% if setting_disable_simple_search == False %}
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-2">
|
||||||
|
{% else %}
|
||||||
|
<div class="col-sm-8">
|
||||||
|
{% endif %}
|
||||||
<select class="form-control" id="selectSearchModel" name="_search_model">
|
<select class="form-control" id="selectSearchModel" name="_search_model">
|
||||||
{% for search_model in search_models %}
|
{% for search_model in search_models %}
|
||||||
{{ search_model.self.get_full_name }}
|
{{ search_model.self.get_full_name }}
|
||||||
@@ -16,12 +28,21 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if setting_disable_simple_search == False %}
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
|
{% else %}
|
||||||
|
<div class="col-sm-4">
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
|
{% if setting_disable_simple_search == False %}
|
||||||
<input class="form-control" name="q" placeholder="{% trans 'Search terms' %}" type="text" value="{{ search_terms|default:'' }}">
|
<input class="form-control" name="q" placeholder="{% trans 'Search terms' %}" type="text" value="{{ search_terms|default:'' }}">
|
||||||
|
{% endif %}
|
||||||
<span class="input-group-btn">
|
<span class="input-group-btn">
|
||||||
|
{% if setting_disable_simple_search == False %}
|
||||||
<button class="btn btn-default" type="submit">{% trans 'Search' %}</button>
|
<button class="btn btn-default" type="submit">{% trans 'Search' %}</button>
|
||||||
<a class="btn btn-primary" href="" id="btnSearchAdvanced" >{% trans 'Advanced' %}</a>
|
{% endif %}
|
||||||
|
<a class="btn btn-primary" href="" id="btnSearchAdvanced" > {% if setting_disable_simple_search == False %}{% trans 'Advanced' %}{% else %}{% trans 'Advanced search' %}{% endif %}</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,6 +51,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if setting_disable_simple_search %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
jQuery(document).ready(function() {
|
jQuery(document).ready(function() {
|
||||||
var $selectSearchModel = $('#selectSearchModel');
|
var $selectSearchModel = $('#selectSearchModel');
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -12,7 +12,9 @@ from mayan.apps.common.menus import (
|
|||||||
menu_tools
|
menu_tools
|
||||||
)
|
)
|
||||||
from mayan.apps.document_indexing.handlers import handler_index_document
|
from mayan.apps.document_indexing.handlers import handler_index_document
|
||||||
from mayan.apps.documents.search import document_page_search, document_search
|
from mayan.apps.documents.search import (
|
||||||
|
document_page_search, document_search, document_version_page_search
|
||||||
|
)
|
||||||
from mayan.apps.documents.signals import post_version_upload
|
from mayan.apps.documents.signals import post_version_upload
|
||||||
from mayan.apps.events.classes import ModelEventType
|
from mayan.apps.events.classes import ModelEventType
|
||||||
from mayan.apps.navigation.classes import SourceColumn
|
from mayan.apps.navigation.classes import SourceColumn
|
||||||
@@ -156,11 +158,19 @@ class FileMetadataApp(MayanAppConfig):
|
|||||||
label=_('File metadata value')
|
label=_('File metadata value')
|
||||||
)
|
)
|
||||||
|
|
||||||
document_page_search.add_model_field(
|
#document_page_search.add_model_field(
|
||||||
|
# field='document__document_version__file_metadata_drivers__entries__key',
|
||||||
|
# label=_('File metadata key')
|
||||||
|
#)
|
||||||
|
#document_page_search.add_model_field(
|
||||||
|
# field='document__document_version__file_metadata_drivers__entries__value',
|
||||||
|
# label=_('File metadata value')
|
||||||
|
#)
|
||||||
|
document_version_page_search.add_model_field(
|
||||||
field='document_version__file_metadata_drivers__entries__key',
|
field='document_version__file_metadata_drivers__entries__key',
|
||||||
label=_('File metadata key')
|
label=_('File metadata key')
|
||||||
)
|
)
|
||||||
document_page_search.add_model_field(
|
document_version_page_search.add_model_field(
|
||||||
field='document_version__file_metadata_drivers__entries__value',
|
field='document_version__file_metadata_drivers__entries__value',
|
||||||
label=_('File metadata value')
|
label=_('File metadata value')
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ from mayan.apps.common.menus import (
|
|||||||
menu_facet, menu_list_facet, menu_multi_item, menu_object, menu_secondary,
|
menu_facet, menu_list_facet, menu_multi_item, menu_object, menu_secondary,
|
||||||
menu_setup
|
menu_setup
|
||||||
)
|
)
|
||||||
from mayan.apps.documents.search import document_page_search, document_search
|
from mayan.apps.documents.search import (
|
||||||
|
document_page_search, document_search, document_version_page_search
|
||||||
|
)
|
||||||
from mayan.apps.documents.signals import post_document_type_change
|
from mayan.apps.documents.signals import post_document_type_change
|
||||||
from mayan.apps.events.classes import ModelEventType
|
from mayan.apps.events.classes import ModelEventType
|
||||||
from mayan.apps.events.links import (
|
from mayan.apps.events.links import (
|
||||||
@@ -76,7 +78,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(
|
||||||
@@ -188,10 +190,18 @@ class MetadataApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
|
|
||||||
document_page_search.add_model_field(
|
document_page_search.add_model_field(
|
||||||
field='document_version__document__metadata__metadata_type__name',
|
field='document__metadata__metadata_type__name',
|
||||||
label=_('Metadata type')
|
label=_('Metadata type')
|
||||||
)
|
)
|
||||||
document_page_search.add_model_field(
|
document_page_search.add_model_field(
|
||||||
|
field='document__metadata__value',
|
||||||
|
label=_('Metadata value')
|
||||||
|
)
|
||||||
|
document_version_page_search.add_model_field(
|
||||||
|
field='document_version__document__metadata__metadata_type__name',
|
||||||
|
label=_('Metadata type')
|
||||||
|
)
|
||||||
|
document_version_page_search.add_model_field(
|
||||||
field='document_version__document__metadata__value',
|
field='document_version__document__metadata__value',
|
||||||
label=_('Metadata value')
|
label=_('Metadata value')
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ from __future__ import unicode_literals
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
DocumentPageOCRContent, DocumentTypeSettings, DocumentVersionOCRError
|
DocumentTypeSettings, DocumentVersionPageOCRContent,
|
||||||
|
DocumentVersionOCRError
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(DocumentPageOCRContent)
|
@admin.register(DocumentVersionPageOCRContent)
|
||||||
class DocumentPageOCRContentAdmin(admin.ModelAdmin):
|
class DocumentVersionPageOCRContentAdmin(admin.ModelAdmin):
|
||||||
list_display = ('document_page',)
|
pass
|
||||||
|
#list_display = ('document_page',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(DocumentTypeSettings)
|
@admin.register(DocumentTypeSettings)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from rest_framework.response import Response
|
|||||||
from mayan.apps.documents.models import Document, DocumentVersion
|
from mayan.apps.documents.models import Document, DocumentVersion
|
||||||
from mayan.apps.rest_api.permissions import MayanPermission
|
from mayan.apps.rest_api.permissions import MayanPermission
|
||||||
|
|
||||||
from .models import DocumentPageOCRContent
|
from .models import DocumentVersionPageOCRContent
|
||||||
from .permissions import permission_ocr_content_view, permission_ocr_document
|
from .permissions import permission_ocr_content_view, permission_ocr_document
|
||||||
from .serializers import DocumentPageOCRContentSerializer
|
from .serializers import DocumentPageOCRContentSerializer
|
||||||
|
|
||||||
@@ -90,8 +90,8 @@ class APIDocumentPageOCRContentView(generics.RetrieveAPIView):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
ocr_content = instance.ocr_content
|
ocr_content = instance.ocr_content
|
||||||
except DocumentPageOCRContent.DoesNotExist:
|
except DocumentVersionPageOCRContent.DoesNotExist:
|
||||||
ocr_content = DocumentPageOCRContent.objects.none()
|
ocr_content = DocumentVersionPageOCRContent.objects.none()
|
||||||
|
|
||||||
serializer = self.get_serializer(ocr_content)
|
serializer = self.get_serializer(ocr_content)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ from mayan.apps.common.classes import ModelField
|
|||||||
from mayan.apps.common.menus import (
|
from mayan.apps.common.menus import (
|
||||||
menu_facet, menu_list_facet, menu_multi_item, menu_secondary, menu_tools
|
menu_facet, menu_list_facet, menu_multi_item, menu_secondary, menu_tools
|
||||||
)
|
)
|
||||||
from mayan.apps.documents.search import document_search, document_page_search
|
from mayan.apps.documents.search import (
|
||||||
|
document_search, document_page_search, document_version_page_search
|
||||||
|
)
|
||||||
from mayan.apps.documents.signals import post_version_upload
|
from mayan.apps.documents.signals import post_version_upload
|
||||||
from mayan.apps.events.classes import ModelEventType
|
from mayan.apps.events.classes import ModelEventType
|
||||||
from mayan.apps.navigation.classes import SourceColumn
|
from mayan.apps.navigation.classes import SourceColumn
|
||||||
@@ -32,17 +34,19 @@ from .links import (
|
|||||||
link_document_ocr_content_delete_multiple, link_document_ocr_download,
|
link_document_ocr_content_delete_multiple, link_document_ocr_download,
|
||||||
link_document_ocr_errors_list, link_document_submit,
|
link_document_ocr_errors_list, link_document_submit,
|
||||||
link_document_submit_multiple, link_document_type_ocr_settings,
|
link_document_submit_multiple, link_document_type_ocr_settings,
|
||||||
link_document_type_submit, link_entry_list
|
link_document_type_submit, link_document_version_page_ocr_content,
|
||||||
|
link_entry_list
|
||||||
)
|
)
|
||||||
from .methods import (
|
from .methods import (
|
||||||
method_document_ocr_submit, method_document_version_ocr_submit
|
method_document_ocr_submit, method_document_page_get_ocr_content,
|
||||||
|
method_document_version_ocr_submit
|
||||||
)
|
)
|
||||||
from .permissions import (
|
from .permissions import (
|
||||||
permission_document_type_ocr_setup, permission_ocr_document,
|
permission_document_type_ocr_setup, permission_ocr_document,
|
||||||
permission_ocr_content_view
|
permission_ocr_content_view
|
||||||
)
|
)
|
||||||
from .signals import post_document_version_ocr
|
from .signals import post_document_version_ocr
|
||||||
from .utils import get_document_ocr_content
|
from .utils import get_document_ocr_content, get_document_version_ocr_content
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -73,6 +77,9 @@ class OCRApp(MayanAppConfig):
|
|||||||
DocumentVersion = apps.get_model(
|
DocumentVersion = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentVersion'
|
app_label='documents', model_name='DocumentVersion'
|
||||||
)
|
)
|
||||||
|
DocumentVersionPage = apps.get_model(
|
||||||
|
app_label='documents', model_name='DocumentVersionPage'
|
||||||
|
)
|
||||||
|
|
||||||
DocumentVersionOCRError = self.get_model(
|
DocumentVersionOCRError = self.get_model(
|
||||||
model_name='DocumentVersionOCRError'
|
model_name='DocumentVersionOCRError'
|
||||||
@@ -81,8 +88,11 @@ class OCRApp(MayanAppConfig):
|
|||||||
Document.add_to_class(
|
Document.add_to_class(
|
||||||
name='submit_for_ocr', value=method_document_ocr_submit
|
name='submit_for_ocr', value=method_document_ocr_submit
|
||||||
)
|
)
|
||||||
|
DocumentPage.add_to_class(
|
||||||
|
name='get_ocr_content', value=method_document_page_get_ocr_content
|
||||||
|
)
|
||||||
DocumentVersion.add_to_class(
|
DocumentVersion.add_to_class(
|
||||||
name='ocr_content', value=get_document_ocr_content
|
name='ocr_content', value=get_document_version_ocr_content
|
||||||
)
|
)
|
||||||
DocumentVersion.add_to_class(
|
DocumentVersion.add_to_class(
|
||||||
name='submit_for_ocr', value=method_document_version_ocr_submit
|
name='submit_for_ocr', value=method_document_version_ocr_submit
|
||||||
@@ -97,7 +107,7 @@ 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(
|
||||||
@@ -128,12 +138,14 @@ class OCRApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
|
|
||||||
document_search.add_model_field(
|
document_search.add_model_field(
|
||||||
field='versions__version_pages__ocr_content__content', label=_('OCR')
|
field='versions__pages__ocr_content__content', label=_('OCR')
|
||||||
)
|
)
|
||||||
|
document_version_page_search.add_model_field(
|
||||||
document_page_search.add_model_field(
|
|
||||||
field='ocr_content__content', label=_('OCR')
|
field='ocr_content__content', label=_('OCR')
|
||||||
)
|
)
|
||||||
|
#document_page_search.add_model_field(
|
||||||
|
# field='ocr_content__content', label=_('OCR')
|
||||||
|
#)
|
||||||
|
|
||||||
menu_facet.bind_links(
|
menu_facet.bind_links(
|
||||||
links=(link_document_ocr_content,), sources=(Document,)
|
links=(link_document_ocr_content,), sources=(Document,)
|
||||||
@@ -141,6 +153,10 @@ class OCRApp(MayanAppConfig):
|
|||||||
menu_list_facet.bind_links(
|
menu_list_facet.bind_links(
|
||||||
links=(link_document_page_ocr_content,), sources=(DocumentPage,)
|
links=(link_document_page_ocr_content,), sources=(DocumentPage,)
|
||||||
)
|
)
|
||||||
|
menu_list_facet.bind_links(
|
||||||
|
links=(link_document_version_page_ocr_content,),
|
||||||
|
sources=(DocumentVersionPage,)
|
||||||
|
)
|
||||||
menu_list_facet.bind_links(
|
menu_list_facet.bind_links(
|
||||||
links=(link_document_type_ocr_settings,), sources=(DocumentType,)
|
links=(link_document_type_ocr_settings,), sources=(DocumentType,)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _, ugettext
|
|||||||
|
|
||||||
from mayan.apps.common.widgets import TextAreaDiv
|
from mayan.apps.common.widgets import TextAreaDiv
|
||||||
|
|
||||||
from .models import DocumentPageOCRContent
|
from .models import DocumentVersionPageOCRContent
|
||||||
|
|
||||||
|
|
||||||
class DocumentPageOCRContentForm(forms.Form):
|
class DocumentPageOCRContentForm(forms.Form):
|
||||||
@@ -28,15 +28,26 @@ class DocumentPageOCRContentForm(forms.Form):
|
|||||||
content = ''
|
content = ''
|
||||||
self.fields['contents'].initial = ''
|
self.fields['contents'].initial = ''
|
||||||
|
|
||||||
try:
|
content = conditional_escape(
|
||||||
page_content = page.ocr_content.content
|
force_text(self.get_instance_ocr_content(instance=page))
|
||||||
except DocumentPageOCRContent.DoesNotExist:
|
)
|
||||||
pass
|
|
||||||
else:
|
|
||||||
content = conditional_escape(force_text(page_content))
|
|
||||||
|
|
||||||
self.fields['contents'].initial = mark_safe(content)
|
self.fields['contents'].initial = mark_safe(content)
|
||||||
|
|
||||||
|
def get_instance_ocr_content(self, instance):
|
||||||
|
try:
|
||||||
|
return instance.content_object.ocr_content.content
|
||||||
|
except DocumentVersionPageOCRContent.DoesNotExist:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentVersionPageOCRContentForm(DocumentPageOCRContentForm):
|
||||||
|
def get_instance_ocr_content(self, instance):
|
||||||
|
try:
|
||||||
|
return instance.ocr_content.content
|
||||||
|
except (AttributeError, DocumentVersionPageOCRContent.DoesNotExist):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
class DocumentOCRContentForm(forms.Form):
|
class DocumentOCRContentForm(forms.Form):
|
||||||
"""
|
"""
|
||||||
@@ -54,19 +65,15 @@ class DocumentOCRContentForm(forms.Form):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.document = kwargs.pop('instance', None)
|
document = kwargs.pop('instance', None)
|
||||||
super(DocumentOCRContentForm, self).__init__(*args, **kwargs)
|
super(DocumentOCRContentForm, self).__init__(*args, **kwargs)
|
||||||
content = []
|
content = []
|
||||||
self.fields['contents'].initial = ''
|
self.fields['contents'].initial = ''
|
||||||
try:
|
|
||||||
document_pages = self.document.pages.all()
|
|
||||||
except AttributeError:
|
|
||||||
document_pages = []
|
|
||||||
|
|
||||||
for page in document_pages:
|
for document_page in document.pages.all():
|
||||||
try:
|
try:
|
||||||
page_content = page.ocr_content.content
|
page_content = document_page.content_object.ocr_content.content
|
||||||
except DocumentPageOCRContent.DoesNotExist:
|
except (AttributeError, DocumentVersionPageOCRContent.DoesNotExist):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
content.append(conditional_escape(force_text(page_content)))
|
content.append(conditional_escape(force_text(page_content)))
|
||||||
@@ -74,7 +81,7 @@ class DocumentOCRContentForm(forms.Form):
|
|||||||
'\n\n\n<hr/><div class="document-page-content-divider">- %s -</div><hr/>\n\n\n' % (
|
'\n\n\n<hr/><div class="document-page-content-divider">- %s -</div><hr/>\n\n\n' % (
|
||||||
ugettext(
|
ugettext(
|
||||||
'Page %(page_number)d'
|
'Page %(page_number)d'
|
||||||
) % {'page_number': page.page_number}
|
) % {'page_number': document_page.page_number}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ icon_document_ocr_errors_list = Icon(
|
|||||||
icon_document_type_ocr_settings = Icon(
|
icon_document_type_ocr_settings = Icon(
|
||||||
driver_name='fontawesome', symbol='font'
|
driver_name='fontawesome', symbol='font'
|
||||||
)
|
)
|
||||||
icon_document_type_submit = Icon(driver_name='fontawesome', symbol='font')
|
|
||||||
icon_entry_list = Icon(driver_name='fontawesome', symbol='font')
|
|
||||||
|
|
||||||
icon_document_submit = icon_document_multiple_submit
|
icon_document_submit = icon_document_multiple_submit
|
||||||
|
icon_document_type_submit = Icon(driver_name='fontawesome', symbol='font')
|
||||||
|
icon_document_version_page_ocr_content = Icon(driver_name='fontawesome', symbol='font')
|
||||||
|
icon_entry_list = Icon(driver_name='fontawesome', symbol='font')
|
||||||
|
|||||||
@@ -58,10 +58,11 @@ link_document_type_submit = Link(
|
|||||||
permissions=(permission_ocr_document,), text=_('OCR documents per type'),
|
permissions=(permission_ocr_document,), text=_('OCR documents per type'),
|
||||||
view='ocr:document_type_submit'
|
view='ocr:document_type_submit'
|
||||||
)
|
)
|
||||||
link_entry_list = Link(
|
link_document_version_page_ocr_content = Link(
|
||||||
icon_class_path='mayan.apps.ocr.icons.icon_entry_list',
|
args='resolved_object.id',
|
||||||
permissions=(permission_ocr_document,), text=_('OCR errors'),
|
icon_class_path='mayan.apps.ocr.icons.icon_document_version_page_ocr_content',
|
||||||
view='ocr:entry_list'
|
permissions=(permission_ocr_content_view,), text=_('OCR'),
|
||||||
|
view='ocr:document_version_page_ocr_content',
|
||||||
)
|
)
|
||||||
link_document_ocr_errors_list = Link(
|
link_document_ocr_errors_list = Link(
|
||||||
args='resolved_object.id',
|
args='resolved_object.id',
|
||||||
@@ -75,3 +76,8 @@ link_document_ocr_download = Link(
|
|||||||
permissions=(permission_ocr_content_view,), text=_('Download OCR text'),
|
permissions=(permission_ocr_content_view,), text=_('Download OCR text'),
|
||||||
view='ocr:document_ocr_download'
|
view='ocr:document_ocr_download'
|
||||||
)
|
)
|
||||||
|
link_entry_list = Link(
|
||||||
|
icon_class_path='mayan.apps.ocr.icons.icon_entry_list',
|
||||||
|
permissions=(permission_ocr_document,), text=_('OCR errors'),
|
||||||
|
view='ocr:entry_list'
|
||||||
|
)
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ from django.conf import settings
|
|||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
|
|
||||||
from mayan.apps.documents.literals import DOCUMENT_IMAGE_TASK_TIMEOUT
|
from mayan.apps.documents.literals import DOCUMENT_IMAGE_TASK_TIMEOUT
|
||||||
from mayan.apps.documents.tasks import task_generate_document_page_image
|
from mayan.apps.documents.tasks import (
|
||||||
|
task_generate_document_version_page_image
|
||||||
|
)
|
||||||
|
|
||||||
from .events import (
|
from .events import (
|
||||||
event_ocr_document_content_deleted, event_ocr_document_version_finish
|
event_ocr_document_content_deleted, event_ocr_document_version_finish
|
||||||
@@ -20,47 +22,53 @@ from .signals import post_document_version_ocr
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DocumentPageOCRContentManager(models.Manager):
|
class DocumentVesionPageOCRContentManager(models.Manager):
|
||||||
def delete_content_for(self, document, user=None):
|
def delete_content_for(self, document, user=None):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
for document_page in document.pages.all():
|
for document_page in document.pages.all():
|
||||||
self.filter(document_page=document_page).delete()
|
self.filter(
|
||||||
|
document_version_page=document_page.content_object
|
||||||
|
).delete()
|
||||||
|
|
||||||
event_ocr_document_content_deleted.commit(
|
event_ocr_document_content_deleted.commit(
|
||||||
actor=user, target=document
|
actor=user, target=document
|
||||||
)
|
)
|
||||||
|
|
||||||
def process_document_page(self, document_page):
|
def process_document_version_page(self, document_version_page):
|
||||||
logger.info(
|
logger.info(
|
||||||
'Processing page: %d of document version: %s',
|
'Processing page: %d of document version: %s',
|
||||||
document_page.page_number, document_page.document_version
|
document_version_page.page_number,
|
||||||
|
document_version_page.document_version
|
||||||
)
|
)
|
||||||
|
|
||||||
DocumentPageOCRContent = apps.get_model(
|
DocumentVersionPageOCRContent = apps.get_model(
|
||||||
app_label='ocr', model_name='DocumentPageOCRContent'
|
app_label='ocr', model_name='DocumentVersionPageOCRContent'
|
||||||
)
|
)
|
||||||
|
|
||||||
task = task_generate_document_page_image.apply_async(
|
task = task_generate_document_version_page_image.apply_async(
|
||||||
kwargs=dict(
|
kwargs=dict(
|
||||||
document_page_id=document_page.pk
|
document_version_page_id=document_version_page.pk
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
cache_filename = task.get(timeout=DOCUMENT_IMAGE_TASK_TIMEOUT, disable_sync_subtasks=False)
|
cache_filename = task.get(
|
||||||
|
timeout=DOCUMENT_IMAGE_TASK_TIMEOUT, disable_sync_subtasks=False
|
||||||
with document_page.cache_partition.get_file(filename=cache_filename).open() as file_object:
|
|
||||||
document_page_content, created = DocumentPageOCRContent.objects.get_or_create(
|
|
||||||
document_page=document_page
|
|
||||||
)
|
)
|
||||||
document_page_content.content = ocr_backend.execute(
|
|
||||||
|
with document_version_page.cache_partition.get_file(filename=cache_filename).open() as file_object:
|
||||||
|
document_version_page_content, created = DocumentVersionPageOCRContent.objects.get_or_create(
|
||||||
|
document_version_page=document_version_page
|
||||||
|
)
|
||||||
|
document_version_page_content.content = ocr_backend.execute(
|
||||||
file_object=file_object,
|
file_object=file_object,
|
||||||
language=document_page.document.language
|
language=document_version_page.document.language
|
||||||
)
|
)
|
||||||
document_page_content.save()
|
document_version_page_content.save()
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
'Finished processing page: %d of document version: %s',
|
'Finished processing page: %d of document version: %s',
|
||||||
document_page.page_number, document_page.document_version
|
document_version_page.page_number,
|
||||||
|
document_version_page.document_version
|
||||||
)
|
)
|
||||||
|
|
||||||
def process_document_version(self, document_version):
|
def process_document_version(self, document_version):
|
||||||
@@ -68,8 +76,10 @@ class DocumentPageOCRContentManager(models.Manager):
|
|||||||
logger.debug('document version: %d', document_version.pk)
|
logger.debug('document version: %d', document_version.pk)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for document_page in document_version.pages.all():
|
for document_version_page in document_version.pages.all():
|
||||||
self.process_document_page(document_page=document_page)
|
self.process_document_version_page(
|
||||||
|
document_version_page=document_version_page
|
||||||
|
)
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
logger.error(
|
logger.error(
|
||||||
'OCR error for document version: %d; %s', document_version.pk,
|
'OCR error for document version: %d; %s', document_version.pk,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
|
||||||
from mayan.apps.common.settings import settings_db_sync_task_delay
|
from mayan.apps.common.settings import settings_db_sync_task_delay
|
||||||
@@ -17,6 +18,17 @@ def method_document_ocr_submit(self):
|
|||||||
latest_version.submit_for_ocr()
|
latest_version.submit_for_ocr()
|
||||||
|
|
||||||
|
|
||||||
|
def method_document_page_get_ocr_content(self):
|
||||||
|
DocumentVersionPageOCRContent = apps.get_model(
|
||||||
|
app_label='ocr', model_name='DocumentVersionPageOCRContent'
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self.content_object.ocr_content.content
|
||||||
|
except (AttributeError, DocumentVersionPageOCRContent.DoesNotExist):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def method_document_version_ocr_submit(self):
|
def method_document_version_ocr_submit(self):
|
||||||
event_ocr_document_version_submit.commit(
|
event_ocr_document_version_submit.commit(
|
||||||
action_object=self.document, target=self
|
action_object=self.document, target=self
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ class Migration(migrations.Migration):
|
|||||||
('documents', '__first__'),
|
('documents', '__first__'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
run_before = [
|
||||||
|
('documents', '0052_rename_document_page'),
|
||||||
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='DocumentVersionOCRError',
|
name='DocumentVersionOCRError',
|
||||||
|
|||||||
39
mayan/apps/ocr/migrations/0009_rename_page_content.py
Normal file
39
mayan/apps/ocr/migrations/0009_rename_page_content.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('ocr', '0008_auto_20180917_0646'),
|
||||||
|
('documents', '0052_rename_document_page'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameModel(
|
||||||
|
'DocumentPageOCRContent', 'DocumentVersionPageOCRContent'
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='documentversionpageocrcontent',
|
||||||
|
name='document_page',
|
||||||
|
field=models.OneToOneField(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name='ocr_content',
|
||||||
|
to='documents.DocumentVersionPage',
|
||||||
|
verbose_name='Document version page'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='documentversionpageocrcontent',
|
||||||
|
old_name='document_page',
|
||||||
|
new_name='document_version_page',
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='documentversionpageocrcontent',
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Document version page OCR content',
|
||||||
|
'verbose_name_plural': 'Document version pages OCR contents'
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -4,10 +4,12 @@ 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 (
|
||||||
|
DocumentType, DocumentVersion, DocumentVersionPage
|
||||||
|
)
|
||||||
|
|
||||||
from .managers import (
|
from .managers import (
|
||||||
DocumentPageOCRContentManager, DocumentTypeSettingsManager
|
DocumentVesionPageOCRContentManager, DocumentTypeSettingsManager
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -36,13 +38,13 @@ class DocumentTypeSettings(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class DocumentPageOCRContent(models.Model):
|
class DocumentVersionPageOCRContent(models.Model):
|
||||||
"""
|
"""
|
||||||
This model stores the OCR results for a document page.
|
This model stores the OCR results for a document page.
|
||||||
"""
|
"""
|
||||||
document_page = models.OneToOneField(
|
document_version_page = models.OneToOneField(
|
||||||
on_delete=models.CASCADE, related_name='ocr_content',
|
on_delete=models.CASCADE, related_name='ocr_content',
|
||||||
to=DocumentPage, verbose_name=_('Document page')
|
to=DocumentVersionPage, verbose_name=_('Document version page')
|
||||||
)
|
)
|
||||||
content = models.TextField(
|
content = models.TextField(
|
||||||
blank=True, help_text=_(
|
blank=True, help_text=_(
|
||||||
@@ -50,11 +52,11 @@ class DocumentPageOCRContent(models.Model):
|
|||||||
), verbose_name=_('Content')
|
), verbose_name=_('Content')
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = DocumentPageOCRContentManager()
|
objects = DocumentVesionPageOCRContentManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Document page OCR content')
|
verbose_name = _('Document version page OCR content')
|
||||||
verbose_name_plural = _('Document pages OCR contents')
|
verbose_name_plural = _('Document version pages OCR contents')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return force_text(self.document_page)
|
return force_text(self.document_page)
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from .models import DocumentPageOCRContent
|
from .models import DocumentVersionPageOCRContent
|
||||||
|
|
||||||
|
|
||||||
class DocumentPageOCRContentSerializer(serializers.ModelSerializer):
|
class DocumentPageOCRContentSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = ('content',)
|
fields = ('content',)
|
||||||
model = DocumentPageOCRContent
|
model = DocumentVersionPageOCRContent
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -12,13 +12,34 @@ from ..permissions import (
|
|||||||
from .literals import TEST_DOCUMENT_CONTENT
|
from .literals import TEST_DOCUMENT_CONTENT
|
||||||
|
|
||||||
|
|
||||||
class OCRAPITestCase(DocumentTestMixin, BaseAPITestCase):
|
class OCRAPIViewTestMixin(object):
|
||||||
def _request_document_ocr_submit_view(self):
|
def _request_document_ocr_submit_view(self):
|
||||||
return self.post(
|
return self.post(
|
||||||
viewname='rest_api:document-ocr-submit-view',
|
viewname='rest_api:document-ocr-submit-view',
|
||||||
kwargs={'pk': self.test_document.pk}
|
kwargs={'pk': self.test_document.pk}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _request_document_version_ocr_submit_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='rest_api:document-version-ocr-submit-view', kwargs={
|
||||||
|
'document_pk': self.test_document.pk,
|
||||||
|
'version_pk': self.test_document.latest_version.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_document_version_page_content_view(self):
|
||||||
|
return self.get(
|
||||||
|
viewname='rest_api:document-page-ocr-content-view', kwargs={
|
||||||
|
'document_pk': self.test_document.pk,
|
||||||
|
'version_pk': self.test_document.latest_version.pk,
|
||||||
|
'page_pk': self.test_document.latest_version.pages.first().pk,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OCRAPIViewTestCase(
|
||||||
|
OCRAPIViewTestMixin, DocumentTestMixin, BaseAPITestCase
|
||||||
|
):
|
||||||
def test_submit_document_no_access(self):
|
def test_submit_document_no_access(self):
|
||||||
response = self._request_document_ocr_submit_view()
|
response = self._request_document_ocr_submit_view()
|
||||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
@@ -35,15 +56,9 @@ class OCRAPITestCase(DocumentTestMixin, BaseAPITestCase):
|
|||||||
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
|
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
|
||||||
|
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
hasattr(self.test_document.pages.first(), 'ocr_content')
|
hasattr(
|
||||||
|
self.test_document.pages.first().content_object, 'ocr_content'
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_document_version_ocr_submit_view(self):
|
|
||||||
return self.post(
|
|
||||||
viewname='rest_api:document-version-ocr-submit-view', kwargs={
|
|
||||||
'document_pk': self.test_document.pk,
|
|
||||||
'version_pk': self.test_document.latest_version.pk
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_submit_document_version_no_access(self):
|
def test_submit_document_version_no_access(self):
|
||||||
@@ -62,20 +77,11 @@ class OCRAPITestCase(DocumentTestMixin, BaseAPITestCase):
|
|||||||
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
|
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
|
||||||
|
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
hasattr(self.test_document.pages.first(), 'ocr_content')
|
hasattr(self.test_document_version.pages.first(), 'ocr_content')
|
||||||
)
|
|
||||||
|
|
||||||
def _request_document_page_content_view(self):
|
|
||||||
return self.get(
|
|
||||||
viewname='rest_api:document-page-ocr-content-view', kwargs={
|
|
||||||
'document_pk': self.test_document.pk,
|
|
||||||
'version_pk': self.test_document.latest_version.pk,
|
|
||||||
'page_pk': self.test_document.latest_version.pages.first().pk,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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 +89,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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class DocumentOCRTestCase(DocumentTestMixin, BaseTestCase):
|
|||||||
_skip_file_descriptor_test = True
|
_skip_file_descriptor_test = True
|
||||||
|
|
||||||
def test_ocr_language_backends_end(self):
|
def test_ocr_language_backends_end(self):
|
||||||
content = self.test_document.pages.first().ocr_content.content
|
content = self.test_document.pages.first().content_object.ocr_content.content
|
||||||
self.assertTrue(TEST_DOCUMENT_CONTENT in content)
|
self.assertTrue(TEST_DOCUMENT_CONTENT in content)
|
||||||
|
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ class GermanOCRSupportTestCase(DocumentTestMixin, BaseTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_ocr_language_backends_end(self):
|
def test_ocr_language_backends_end(self):
|
||||||
content = self.test_document.pages.first().ocr_content.content
|
content = self.test_document.pages.first().content_object.ocr_content.content
|
||||||
|
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
TEST_DOCUMENT_CONTENT_DEU_1 in content
|
TEST_DOCUMENT_CONTENT_DEU_1 in content
|
||||||
|
|||||||
@@ -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,10 +27,10 @@ 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_version.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().content_object
|
||||||
).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().content_object
|
||||||
).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):
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ from .api_views import (
|
|||||||
)
|
)
|
||||||
from .views import (
|
from .views import (
|
||||||
DocumentOCRContentDeleteView, DocumentOCRContentView,
|
DocumentOCRContentDeleteView, DocumentOCRContentView,
|
||||||
DocumentOCRDownloadView,
|
DocumentOCRDownloadView, DocumentOCRErrorsListView,
|
||||||
DocumentOCRErrorsListView, DocumentPageOCRContentView, DocumentSubmitView,
|
DocumentPageOCRContentView, DocumentSubmitView,
|
||||||
DocumentTypeSettingsEditView, DocumentTypeSubmitView, EntryListView
|
DocumentTypeSettingsEditView, DocumentTypeSubmitView,
|
||||||
|
DocumentVersionPageOCRContentView, EntryListView
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@@ -50,6 +51,11 @@ urlpatterns = [
|
|||||||
view=DocumentPageOCRContentView.as_view(),
|
view=DocumentPageOCRContentView.as_view(),
|
||||||
name='document_page_ocr_content'
|
name='document_page_ocr_content'
|
||||||
),
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^documents/versions/pages/(?P<pk>\d+)/content/$',
|
||||||
|
view=DocumentVersionPageOCRContentView.as_view(),
|
||||||
|
name='document_version_page_ocr_content'
|
||||||
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^document_types/submit/$',
|
regex=r'^document_types/submit/$',
|
||||||
view=DocumentTypeSubmitView.as_view(), name='document_type_submit'
|
view=DocumentTypeSubmitView.as_view(), name='document_type_submit'
|
||||||
|
|||||||
@@ -4,15 +4,29 @@ from django.apps import apps
|
|||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
|
|
||||||
|
|
||||||
def get_document_ocr_content(document):
|
def get_document_version_ocr_content(document_version):
|
||||||
DocumentPageOCRContent = apps.get_model(
|
DocumentVersionPageOCRContent = apps.get_model(
|
||||||
app_label='ocr', model_name='DocumentPageOCRContent'
|
app_label='ocr', model_name='DocumentVersionPageOCRContent'
|
||||||
)
|
)
|
||||||
|
|
||||||
for page in document.pages.all():
|
for document_version_page in document_version.pages.all():
|
||||||
try:
|
try:
|
||||||
page_content = page.ocr_content.content
|
page_content = document_version_page.ocr_content.content
|
||||||
except DocumentPageOCRContent.DoesNotExist:
|
except DocumentVersionPageOCRContent.DoesNotExist:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
yield force_text(page_content)
|
||||||
|
|
||||||
|
|
||||||
|
def get_document_ocr_content(document):
|
||||||
|
DocumentVersionPageOCRContent = apps.get_model(
|
||||||
|
app_label='ocr', model_name='DocumentVersionPageOCRContent'
|
||||||
|
)
|
||||||
|
|
||||||
|
for document_page in document.pages.all():
|
||||||
|
try:
|
||||||
|
page_content = document_page.content_object.ocr_content.content
|
||||||
|
except (AttributeError, DocumentVersionPageOCRContent.DoesNotExist):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
yield force_text(page_content)
|
yield force_text(page_content)
|
||||||
|
|||||||
@@ -12,10 +12,15 @@ 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, DocumentPage, DocumentType, DocumentVersionPage
|
||||||
|
)
|
||||||
|
|
||||||
from .forms import DocumentPageOCRContentForm, DocumentOCRContentForm
|
from .forms import (
|
||||||
from .models import DocumentPageOCRContent, DocumentVersionOCRError
|
DocumentPageOCRContentForm, DocumentOCRContentForm,
|
||||||
|
DocumentVersionPageOCRContentForm
|
||||||
|
)
|
||||||
|
from .models import DocumentVersionPageOCRContent, DocumentVersionOCRError
|
||||||
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
|
||||||
@@ -46,7 +51,7 @@ class DocumentOCRContentDeleteView(MultipleObjectConfirmActionView):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def object_action(self, form, instance):
|
def object_action(self, form, instance):
|
||||||
DocumentPageOCRContent.objects.delete_content_for(
|
DocumentVersionPageOCRContent.objects.delete_content_for(
|
||||||
document=instance, user=self.request.user
|
document=instance, user=self.request.user
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -94,6 +99,30 @@ class DocumentPageOCRContentView(SingleObjectDetailView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentVersionPageOCRContentView(SingleObjectDetailView):
|
||||||
|
form_class = DocumentVersionPageOCRContentForm
|
||||||
|
model = DocumentVersionPage
|
||||||
|
object_permission = permission_ocr_content_view
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
result = super(DocumentVersionPageOCRContentView, self).dispatch(
|
||||||
|
request, *args, **kwargs
|
||||||
|
)
|
||||||
|
self.get_object().document.add_as_recent_document_for_user(
|
||||||
|
user=request.user
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'hide_labels': True,
|
||||||
|
'object': self.get_object(),
|
||||||
|
'title': _(
|
||||||
|
'OCR result for document version page: %s'
|
||||||
|
) % self.get_object(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class DocumentSubmitView(MultipleObjectConfirmActionView):
|
class DocumentSubmitView(MultipleObjectConfirmActionView):
|
||||||
model = Document
|
model = Document
|
||||||
object_permission = permission_ocr_document
|
object_permission = permission_ocr_document
|
||||||
|
|||||||
@@ -21,9 +21,10 @@ from .handlers import (
|
|||||||
handler_create_default_document_source, handler_initialize_periodic_tasks
|
handler_create_default_document_source, handler_initialize_periodic_tasks
|
||||||
)
|
)
|
||||||
from .links import (
|
from .links import (
|
||||||
link_document_create_multiple, link_setup_sources,
|
link_document_create_multiple, link_document_pages_append,
|
||||||
link_setup_source_check_now, link_setup_source_create_imap_email,
|
link_setup_sources, link_setup_source_check_now,
|
||||||
link_setup_source_create_pop3_email, link_setup_source_create_sane_scanner,
|
link_setup_source_create_imap_email, link_setup_source_create_pop3_email,
|
||||||
|
link_setup_source_create_sane_scanner,
|
||||||
link_setup_source_create_watch_folder, link_setup_source_create_webform,
|
link_setup_source_create_watch_folder, link_setup_source_create_webform,
|
||||||
link_setup_source_create_staging_folder, link_setup_source_delete,
|
link_setup_source_create_staging_folder, link_setup_source_delete,
|
||||||
link_setup_source_edit, link_setup_source_logs, link_staging_file_delete,
|
link_setup_source_edit, link_setup_source_logs, link_staging_file_delete,
|
||||||
@@ -150,6 +151,12 @@ class SourcesApp(MayanAppConfig):
|
|||||||
'sources:document_version_upload'
|
'sources:document_version_upload'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
menu_secondary.bind_links(
|
||||||
|
links=(link_document_pages_append,),
|
||||||
|
sources=(
|
||||||
|
'documents:document_pages', 'sources:document_pages_append'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
post_upgrade.connect(
|
post_upgrade.connect(
|
||||||
receiver=handler_initialize_periodic_tasks,
|
receiver=handler_initialize_periodic_tasks,
|
||||||
|
|||||||
@@ -28,6 +28,19 @@ class NewVersionForm(forms.Form):
|
|||||||
label=_('Comment'), required=False,
|
label=_('Comment'), required=False,
|
||||||
widget=forms.widgets.Textarea(attrs={'rows': 4}),
|
widget=forms.widgets.Textarea(attrs={'rows': 4}),
|
||||||
)
|
)
|
||||||
|
append_pages = forms.BooleanField(
|
||||||
|
help_text=_(
|
||||||
|
'If selected, the pages of the file uploaded will be appended '
|
||||||
|
'to the existing document pages. Otherwise the pages of the '
|
||||||
|
'upload will replace the existing pages of the document.'
|
||||||
|
), label=_('Append pages?'), required=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
hide_append_pages = kwargs.pop('hide_append_pages', False)
|
||||||
|
super(NewVersionForm, self).__init__(*args, **kwargs)
|
||||||
|
if hide_append_pages:
|
||||||
|
self.fields['append_pages'].widget = forms.widgets.HiddenInput()
|
||||||
|
|
||||||
|
|
||||||
class UploadBaseForm(forms.Form):
|
class UploadBaseForm(forms.Form):
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ icon_document_version_upload = Icon(
|
|||||||
driver_name='fontawesome', symbol='upload'
|
driver_name='fontawesome', symbol='upload'
|
||||||
)
|
)
|
||||||
icon_log = Icon(driver_name='fontawesome', symbol='exclamation-triangle')
|
icon_log = Icon(driver_name='fontawesome', symbol='exclamation-triangle')
|
||||||
|
icon_document_pages_append = Icon(
|
||||||
|
driver_name='fontawesome-dual', primary_symbol='copy',
|
||||||
|
secondary_symbol='plus'
|
||||||
|
)
|
||||||
icon_setup_sources = Icon(driver_name='fontawesome', symbol='upload')
|
icon_setup_sources = Icon(driver_name='fontawesome', symbol='upload')
|
||||||
icon_setup_source_check_now = Icon(driver_name='fontawesome', symbol='check')
|
icon_setup_source_check_now = Icon(driver_name='fontawesome', symbol='check')
|
||||||
icon_setup_source_delete = Icon(driver_name='fontawesome', symbol='times')
|
icon_setup_source_delete = Icon(driver_name='fontawesome', symbol='times')
|
||||||
|
|||||||
@@ -113,6 +113,13 @@ link_staging_file_delete = Link(
|
|||||||
permissions=(permission_document_new_version, permission_document_create),
|
permissions=(permission_document_new_version, permission_document_create),
|
||||||
tags='dangerous', text=_('Delete'), view='sources:staging_file_delete',
|
tags='dangerous', text=_('Delete'), view='sources:staging_file_delete',
|
||||||
)
|
)
|
||||||
|
link_document_pages_append = Link(
|
||||||
|
args='resolved_object.pk', condition=document_new_version_not_blocked,
|
||||||
|
icon_class_path='mayan.apps.sources.icons.icon_document_pages_append',
|
||||||
|
permissions=(permission_document_new_version,),
|
||||||
|
text=_('Append pages'),
|
||||||
|
view='sources:document_pages_append'
|
||||||
|
)
|
||||||
link_document_version_upload = Link(
|
link_document_version_upload = Link(
|
||||||
args='resolved_object.pk', condition=document_new_version_not_blocked,
|
args='resolved_object.pk', condition=document_new_version_not_blocked,
|
||||||
icon_class_path='mayan.apps.sources.icons.icon_document_version_upload',
|
icon_class_path='mayan.apps.sources.icons.icon_document_version_upload',
|
||||||
|
|||||||
115
mayan/apps/sources/templates/sources/dropzone.html
Normal file
115
mayan/apps/sources/templates/sources/dropzone.html
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<link href="{% static 'sources/node_modules/dropzone/dist/dropzone.css' %}" media="screen" rel="stylesheet" type="text/css" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dropzone .dz-preview .dz-details {
|
||||||
|
top: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone .dz-preview .dz-progress {
|
||||||
|
top: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone .dz-preview .dz-error-message {
|
||||||
|
top: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-previews, .dropzone .dz-message {
|
||||||
|
border: 2px solid rgba(0, 0, 0, 0.3);
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone .dz-preview .dz-error-message {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
padding: 1.2em;
|
||||||
|
top: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone .dz-preview .dz-details .dz-filename span,
|
||||||
|
.dropzone .dz-preview .dz-details .dz-size span {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone {
|
||||||
|
min-height: 150px;
|
||||||
|
border: inherit;
|
||||||
|
background: inherit;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-previews, .dropzone .dz-message {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script type="text/x-template" id="previewTemplate">
|
||||||
|
<div class="dz-preview dz-file-preview">
|
||||||
|
<i class="far fa-file fa-10x"></i>
|
||||||
|
<div class="dz-details">
|
||||||
|
<div class="dz-filename"><span data-dz-name></span></div>
|
||||||
|
<div class="dz-size" data-dz-size></div>
|
||||||
|
<img data-dz-thumbnail />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dz-progress">
|
||||||
|
<span class="dz-upload" data-dz-uploadprogress>
|
||||||
|
<div class="progress-bar progress-bar-success progress-bar-striped active" role="progressbar" style="width: 100%">
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dz-success-mark">
|
||||||
|
<span>
|
||||||
|
<i class="text-success fa fa-4x fa-check-circle"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="dz-error-mark">
|
||||||
|
<span>
|
||||||
|
<i class="text-danger fa fa-4x fa-times-circle"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="dz-error-message">
|
||||||
|
<span data-dz-errormessage>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var messageCancelUpload = "{% trans 'Cancel upload' %}";
|
||||||
|
var messageCancelUploadConfirmation = "{% trans 'Are you sure you want to cancel this upload?' %}";
|
||||||
|
var messageDefaultMessage = "{% trans 'Drop files or click here to upload files' %}";
|
||||||
|
var messageFallbackMessage = "{% trans 'Your browser does not support drag and drop file uploads.' %}";
|
||||||
|
var messageFallbackText = "{% trans 'Please use the fallback form below to upload your files.' %}";
|
||||||
|
var messageRemoveFile = "{% trans 'Clear' %}";
|
||||||
|
var messageResponseError = "{% trans 'Server responded with {{statusCode}} code.' %}";
|
||||||
|
|
||||||
|
$.getScript( "{% static 'sources/node_modules/dropzone/dist/dropzone.js' %}" )
|
||||||
|
.done(function( script, textStatus ) {
|
||||||
|
Dropzone.autoDiscover = false;
|
||||||
|
jQuery(document).ready(function() {
|
||||||
|
var previewTemplate = document.querySelector('#previewTemplate').innerHTML;
|
||||||
|
|
||||||
|
{% verbatim %}
|
||||||
|
$('.dropzone').dropzone({
|
||||||
|
addRemoveLinks: true,
|
||||||
|
createImageThumbnails: false,
|
||||||
|
dictCancelUpload: messageCancelUpload,
|
||||||
|
dictCancelUploadConfirmation: messageCancelUploadConfirmation,
|
||||||
|
dictDefaultMessage: '<i class="fa fa-cloud-upload-alt"></i> ' + messageDefaultMessage,
|
||||||
|
dictFallbackMessage: messageFallbackMessage,
|
||||||
|
dictFallbackText: messageFallbackText,
|
||||||
|
dictRemoveFile: messageRemoveFile,
|
||||||
|
dictResponseError: messageResponseError,
|
||||||
|
maxFilesize: 2048,
|
||||||
|
paramName: 'source-file',
|
||||||
|
previewTemplate: previewTemplate,
|
||||||
|
timeout: 1200000
|
||||||
|
});
|
||||||
|
{% endverbatim %}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -1,118 +1,2 @@
|
|||||||
{% load i18n %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
<link href="{% static 'sources/node_modules/dropzone/dist/dropzone.css' %}" media="screen" rel="stylesheet" type="text/css" />
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.dropzone .dz-preview .dz-details {
|
|
||||||
top: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropzone .dz-preview .dz-progress {
|
|
||||||
top: 60%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropzone .dz-preview .dz-error-message {
|
|
||||||
top: 130px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropzone-previews, .dropzone .dz-message {
|
|
||||||
border: 2px solid rgba(0, 0, 0, 0.3);
|
|
||||||
padding: 40px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropzone .dz-preview .dz-error-message {
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
padding: 1.2em;
|
|
||||||
top: 180px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropzone .dz-preview .dz-details .dz-filename span,
|
|
||||||
.dropzone .dz-preview .dz-details .dz-size span {
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropzone {
|
|
||||||
min-height: 150px;
|
|
||||||
border: inherit;
|
|
||||||
background: inherit;
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropzone-previews, .dropzone .dz-message {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
{% include 'appearance/generic_multiform_subtemplate.html' %}
|
{% include 'appearance/generic_multiform_subtemplate.html' %}
|
||||||
|
{% include 'sources/dropzone.html' %}
|
||||||
<script type="text/x-template" id="previewTemplate">
|
|
||||||
<div class="dz-preview dz-file-preview">
|
|
||||||
<i class="far fa-file fa-10x"></i>
|
|
||||||
<div class="dz-details">
|
|
||||||
<div class="dz-filename"><span data-dz-name></span></div>
|
|
||||||
<div class="dz-size" data-dz-size></div>
|
|
||||||
<img data-dz-thumbnail />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dz-progress">
|
|
||||||
<span class="dz-upload" data-dz-uploadprogress>
|
|
||||||
<div class="progress-bar progress-bar-success progress-bar-striped active" role="progressbar" style="width: 100%">
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dz-success-mark">
|
|
||||||
<span>
|
|
||||||
<i class="text-success fa fa-4x fa-check-circle"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="dz-error-mark">
|
|
||||||
<span>
|
|
||||||
<i class="text-danger fa fa-4x fa-times-circle"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="dz-error-message">
|
|
||||||
<span data-dz-errormessage>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
var messageCancelUpload = "{% trans 'Cancel upload' %}";
|
|
||||||
var messageCancelUploadConfirmation = "{% trans 'Are you sure you want to cancel this upload?' %}";
|
|
||||||
var messageDefaultMessage = "{% trans 'Drop files or click here to upload files' %}";
|
|
||||||
var messageFallbackMessage = "{% trans 'Your browser does not support drag and drop file uploads.' %}";
|
|
||||||
var messageFallbackText = "{% trans 'Please use the fallback form below to upload your files.' %}";
|
|
||||||
var messageRemoveFile = "{% trans 'Clear' %}";
|
|
||||||
var messageResponseError = "{% trans 'Server responded with {{statusCode}} code.' %}";
|
|
||||||
|
|
||||||
$.getScript( "{% static 'sources/node_modules/dropzone/dist/dropzone.js' %}" )
|
|
||||||
.done(function( script, textStatus ) {
|
|
||||||
Dropzone.autoDiscover = false;
|
|
||||||
jQuery(document).ready(function() {
|
|
||||||
var previewTemplate = document.querySelector('#previewTemplate').innerHTML;
|
|
||||||
|
|
||||||
{% verbatim %}
|
|
||||||
$('.dropzone').dropzone({
|
|
||||||
addRemoveLinks: true,
|
|
||||||
createImageThumbnails: false,
|
|
||||||
dictCancelUpload: messageCancelUpload,
|
|
||||||
dictCancelUploadConfirmation: messageCancelUploadConfirmation,
|
|
||||||
dictDefaultMessage: '<i class="fa fa-cloud-upload-alt"></i> ' + messageDefaultMessage,
|
|
||||||
dictFallbackMessage: messageFallbackMessage,
|
|
||||||
dictFallbackText: messageFallbackText,
|
|
||||||
dictRemoveFile: messageRemoveFile,
|
|
||||||
dictResponseError: messageResponseError,
|
|
||||||
maxFilesize: 2048,
|
|
||||||
paramName: 'source-file',
|
|
||||||
previewTemplate: previewTemplate,
|
|
||||||
timeout: 1200000
|
|
||||||
});
|
|
||||||
{% endverbatim %}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ from .literals import TEST_SOURCE_LABEL, TEST_SOURCE_UNCOMPRESS_N
|
|||||||
|
|
||||||
|
|
||||||
class SourceTestMixin(object):
|
class SourceTestMixin(object):
|
||||||
|
auto_create_test_source = True
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(SourceTestMixin, self).setUp()
|
||||||
|
if self.auto_create_test_source:
|
||||||
|
self._create_test_source()
|
||||||
|
|
||||||
def _create_test_source(self):
|
def _create_test_source(self):
|
||||||
self.test_source = WebFormSource.objects.create(
|
self.test_source = WebFormSource.objects.create(
|
||||||
enabled=True, label=TEST_SOURCE_LABEL,
|
enabled=True, label=TEST_SOURCE_LABEL,
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ class CompressedUploadsTestCase(SourceTestMixin, GenericDocumentTestCase):
|
|||||||
auto_upload_document = False
|
auto_upload_document = False
|
||||||
|
|
||||||
def test_upload_compressed_file(self):
|
def test_upload_compressed_file(self):
|
||||||
self._create_test_source()
|
|
||||||
self.test_source.uncompress = SOURCE_UNCOMPRESS_CHOICE_Y
|
self.test_source.uncompress = SOURCE_UNCOMPRESS_CHOICE_Y
|
||||||
self.test_source.save()
|
self.test_source.save()
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import shutil
|
|||||||
from mayan.apps.checkouts.models import NewVersionBlock
|
from mayan.apps.checkouts.models import NewVersionBlock
|
||||||
from mayan.apps.common.tests.base import GenericViewTestCase
|
from mayan.apps.common.tests.base import GenericViewTestCase
|
||||||
from mayan.apps.documents.models import Document
|
from mayan.apps.documents.models import Document
|
||||||
from mayan.apps.documents.permissions import permission_document_create
|
from mayan.apps.documents.permissions import (
|
||||||
|
permission_document_create, permission_document_new_version
|
||||||
|
)
|
||||||
from mayan.apps.documents.tests.base import GenericDocumentViewTestCase
|
from mayan.apps.documents.tests.base import GenericDocumentViewTestCase
|
||||||
from mayan.apps.documents.tests.literals import (
|
from mayan.apps.documents.tests.literals import (
|
||||||
TEST_COMPRESSED_DOCUMENT_PATH, TEST_DOCUMENT_DESCRIPTION,
|
TEST_COMPRESSED_DOCUMENT_PATH, TEST_DOCUMENT_DESCRIPTION,
|
||||||
@@ -28,6 +30,44 @@ from .literals import (
|
|||||||
from .mixins import SourceTestMixin, SourceViewTestMixin
|
from .mixins import SourceTestMixin, SourceViewTestMixin
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentViewTestMixin(object):
|
||||||
|
def _request_test_document_append_pages_view(self):
|
||||||
|
with open(TEST_SMALL_DOCUMENT_PATH, mode='rb') as file_object:
|
||||||
|
return self.post(
|
||||||
|
viewname='sources:document_pages_append', kwargs={
|
||||||
|
'document_pk': self.test_document.pk
|
||||||
|
}, data={
|
||||||
|
'source-file': file_object,
|
||||||
|
'document-append_pages': True, # Needs to be explicit
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentViewTestCase(
|
||||||
|
SourceTestMixin, DocumentViewTestMixin, GenericDocumentViewTestCase
|
||||||
|
):
|
||||||
|
def test_document_append_pages_view_no_permission(self):
|
||||||
|
page_count = self.test_document.pages.count()
|
||||||
|
|
||||||
|
response = self._request_test_document_append_pages_view()
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
self.assertEqual(self.test_document.pages.count(), page_count)
|
||||||
|
|
||||||
|
def test_document_append_pages_view_with_access(self):
|
||||||
|
page_count = self.test_document.pages.count()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document,
|
||||||
|
permission=permission_document_new_version
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_append_pages_view()
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
self.assertTrue(self.test_document.pages.count() > page_count)
|
||||||
|
|
||||||
|
|
||||||
class DocumentUploadWizardViewTestMixin(object):
|
class DocumentUploadWizardViewTestMixin(object):
|
||||||
def _request_upload_wizard_view(self, document_path=TEST_SMALL_DOCUMENT_PATH):
|
def _request_upload_wizard_view(self, document_path=TEST_SMALL_DOCUMENT_PATH):
|
||||||
with open(document_path, mode='rb') as file_object:
|
with open(document_path, mode='rb') as file_object:
|
||||||
@@ -54,10 +94,6 @@ class DocumentUploadWizardViewTestCase(
|
|||||||
):
|
):
|
||||||
auto_upload_document = False
|
auto_upload_document = False
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(DocumentUploadWizardViewTestCase, self).setUp()
|
|
||||||
self._create_test_source()
|
|
||||||
|
|
||||||
def test_upload_compressed_file(self):
|
def test_upload_compressed_file(self):
|
||||||
self.test_source.uncompress = SOURCE_UNCOMPRESS_CHOICE_Y
|
self.test_source.uncompress = SOURCE_UNCOMPRESS_CHOICE_Y
|
||||||
self.test_source.save()
|
self.test_source.save()
|
||||||
@@ -188,7 +224,7 @@ class DocumentUploadIssueTestCase(GenericDocumentViewTestCase):
|
|||||||
self.assertEqual(document.description, TEST_DOCUMENT_DESCRIPTION)
|
self.assertEqual(document.description, TEST_DOCUMENT_DESCRIPTION)
|
||||||
|
|
||||||
|
|
||||||
class NewDocumentVersionViewTestCase(GenericDocumentViewTestCase):
|
class DocumentVersionUploadViewTestCase(GenericDocumentViewTestCase):
|
||||||
auto_login_superuser = True
|
auto_login_superuser = True
|
||||||
auto_login_user = False
|
auto_login_user = False
|
||||||
create_test_case_superuser = True
|
create_test_case_superuser = True
|
||||||
@@ -300,6 +336,8 @@ class StagingFolderViewTestCase(
|
|||||||
class SourcesViewTestCase(
|
class SourcesViewTestCase(
|
||||||
SourceTestMixin, SourceViewTestMixin, GenericViewTestCase
|
SourceTestMixin, SourceViewTestMixin, GenericViewTestCase
|
||||||
):
|
):
|
||||||
|
auto_create_test_source = False
|
||||||
|
|
||||||
def test_source_create_view_no_permission(self):
|
def test_source_create_view_no_permission(self):
|
||||||
response = self._request_setup_source_create_view()
|
response = self._request_setup_source_create_view()
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ from .api_views import (
|
|||||||
APIStagingSourceListView, APIStagingSourceView
|
APIStagingSourceListView, APIStagingSourceView
|
||||||
)
|
)
|
||||||
from .views import (
|
from .views import (
|
||||||
|
DocumentPagesAppendView, DocumentVersionUploadInteractiveView,
|
||||||
SetupSourceCheckView, SetupSourceCreateView, SetupSourceDeleteView,
|
SetupSourceCheckView, SetupSourceCreateView, SetupSourceDeleteView,
|
||||||
SetupSourceEditView, SetupSourceListView, SourceLogListView,
|
SetupSourceEditView, SetupSourceListView, SourceLogListView,
|
||||||
StagingFileDeleteView, DocumentVersionUploadInteractiveView, UploadInteractiveView
|
StagingFileDeleteView, UploadInteractiveView
|
||||||
)
|
)
|
||||||
from .wizards import DocumentCreateWizard
|
from .wizards import DocumentCreateWizard
|
||||||
|
|
||||||
@@ -45,6 +46,14 @@ urlpatterns = [
|
|||||||
view=DocumentVersionUploadInteractiveView.as_view(),
|
view=DocumentVersionUploadInteractiveView.as_view(),
|
||||||
name='document_version_upload'
|
name='document_version_upload'
|
||||||
),
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^documents/(?P<document_pk>\d+)/pages/append/interactive/(?P<source_id>\d+)/$',
|
||||||
|
view=DocumentPagesAppendView.as_view(), name='document_pages_append'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^documents/(?P<document_pk>\d+)/pages/append/interactive/$',
|
||||||
|
view=DocumentPagesAppendView.as_view(), name='document_pages_append'
|
||||||
|
),
|
||||||
|
|
||||||
# Setup views
|
# Setup views
|
||||||
|
|
||||||
|
|||||||
@@ -362,7 +362,6 @@ class UploadInteractiveView(UploadBaseView):
|
|||||||
|
|
||||||
class DocumentVersionUploadInteractiveView(UploadBaseView):
|
class DocumentVersionUploadInteractiveView(UploadBaseView):
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
|
||||||
self.subtemplates_list = []
|
self.subtemplates_list = []
|
||||||
|
|
||||||
self.document = get_object_or_404(
|
self.document = get_object_or_404(
|
||||||
@@ -417,12 +416,17 @@ class DocumentVersionUploadInteractiveView(UploadBaseView):
|
|||||||
else:
|
else:
|
||||||
user_id = None
|
user_id = None
|
||||||
|
|
||||||
task_upload_new_version.apply_async(kwargs=dict(
|
task_upload_new_version.apply_async(
|
||||||
|
kwargs=dict(
|
||||||
|
append_pages=forms['document_form'].cleaned_data.get(
|
||||||
|
'append_pages', False
|
||||||
|
),
|
||||||
shared_uploaded_file_id=shared_uploaded_file.pk,
|
shared_uploaded_file_id=shared_uploaded_file.pk,
|
||||||
document_id=self.document.pk,
|
document_id=self.document.pk,
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
comment=forms['document_form'].cleaned_data.get('comment')
|
comment=forms['document_form'].cleaned_data.get('comment')
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
messages.success(
|
messages.success(
|
||||||
message=_(
|
message=_(
|
||||||
@@ -449,9 +453,15 @@ class DocumentVersionUploadInteractiveView(UploadBaseView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_form_classes(self):
|
def get_form_classes(self):
|
||||||
|
source_form_class = get_upload_form_class(self.source.source_type)
|
||||||
|
|
||||||
|
# Override source form class to enable the HTML5 file uploader
|
||||||
|
if source_form_class == WebFormUploadForm:
|
||||||
|
source_form_class = WebFormUploadFormHTML5
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'document_form': NewVersionForm,
|
'document_form': NewVersionForm,
|
||||||
'source_form': get_upload_form_class(self.source.source_type)
|
'source_form': source_form_class
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
@@ -464,6 +474,37 @@ class DocumentVersionUploadInteractiveView(UploadBaseView):
|
|||||||
'from source: %(source)s'
|
'from source: %(source)s'
|
||||||
) % {'document': self.document, 'source': self.source.label}
|
) % {'document': self.document, 'source': self.source.label}
|
||||||
context['submit_label'] = _('Submit')
|
context['submit_label'] = _('Submit')
|
||||||
|
context['form_css_classes'] = 'dropzone'
|
||||||
|
context['form_disable_submit'] = True
|
||||||
|
context['form_action'] = '{}?{}'.format(
|
||||||
|
reverse(
|
||||||
|
viewname=self.request.resolver_match.view_name,
|
||||||
|
kwargs=self.request.resolver_match.kwargs
|
||||||
|
), self.request.META['QUERY_STRING']
|
||||||
|
)
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentPagesAppendView(DocumentVersionUploadInteractiveView):
|
||||||
|
def get_document_form_initial(self):
|
||||||
|
return {
|
||||||
|
'append_pages': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_form_extra_kwargs(self, form_name):
|
||||||
|
if form_name == 'document_form':
|
||||||
|
return {
|
||||||
|
'hide_append_pages': True
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(
|
||||||
|
DocumentPagesAppendView, self
|
||||||
|
).get_context_data(**kwargs)
|
||||||
|
context['title'] = _(
|
||||||
|
'Append pages to document "%(document)s" from source: %(source)s'
|
||||||
|
) % {'document': self.document, 'source': self.source.label}
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ from mayan.apps.common.menus import (
|
|||||||
menu_facet, menu_list_facet, menu_main, menu_multi_item, menu_object,
|
menu_facet, menu_list_facet, menu_main, menu_multi_item, menu_object,
|
||||||
menu_secondary
|
menu_secondary
|
||||||
)
|
)
|
||||||
from mayan.apps.documents.search import document_page_search, document_search
|
from mayan.apps.documents.search import (
|
||||||
|
document_page_search, document_search, document_version_page_search
|
||||||
|
)
|
||||||
from mayan.apps.events.classes import ModelEventType
|
from mayan.apps.events.classes import ModelEventType
|
||||||
from mayan.apps.events.links import (
|
from mayan.apps.events.links import (
|
||||||
link_events_for_object, link_object_event_types_user_subcriptions_list,
|
link_events_for_object, link_object_event_types_user_subcriptions_list,
|
||||||
@@ -62,7 +64,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')
|
||||||
@@ -133,9 +135,12 @@ class TagsApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
|
|
||||||
document_page_search.add_model_field(
|
document_page_search.add_model_field(
|
||||||
field='document_version__document__tags__label', label=_('Tags')
|
field='document__tags__label', label=_('Tags')
|
||||||
)
|
)
|
||||||
document_search.add_model_field(field='tags__label', label=_('Tags'))
|
document_search.add_model_field(field='tags__label', label=_('Tags'))
|
||||||
|
document_version_page_search.add_model_field(
|
||||||
|
field='document_version__document__tags__label', label=_('Tags')
|
||||||
|
)
|
||||||
|
|
||||||
menu_facet.bind_links(
|
menu_facet.bind_links(
|
||||||
links=(link_document_tag_list,), sources=(Document,)
|
links=(link_document_tag_list,), sources=(Document,)
|
||||||
|
|||||||
Reference in New Issue
Block a user