Add support for new document page structure
Documents now have their own dedicated DocumentPage submodel. The old DocumentPage is now called DocumentVersionPage. This allows mappings between document pages and document version pages, allowing renumbering, appending pages. DocumentPages have a content_object to map them to any other object. For now they only map to DocumentVersionPages. New option added to the version upload form to append the pages of the new version. A new view was added to just append new pages with wraps the new document version upload form and hides the append pages checkbox set to True. Add a new action, reset_pages to reset the pages of the document to those of the latest version. Missing: appending tests, checks for proper content_object in OCR and document parsing. Author: Roberto Rosario <roberto.rosario@mayan-edms.com> Date: Thu Oct 11 12:00:25 2019 -0400
This commit is contained in:
@@ -93,7 +93,7 @@ Changes
|
|||||||
Removals
|
Removals
|
||||||
--------
|
--------
|
||||||
|
|
||||||
- Database conversion. Reason for removal. The database conversions support
|
- Database conversion. Reason for removal: The database conversions support
|
||||||
provided by this feature (SQLite to PostgreSQL) was being confused with
|
provided by this feature (SQLite to PostgreSQL) was being confused with
|
||||||
database migrations and upgrades.
|
database migrations and upgrades.
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ Removals
|
|||||||
|
|
||||||
Continued confusion about the purpose of the feature and confusion about
|
Continued confusion about the purpose of the feature and confusion about
|
||||||
how errors with this feature were a reflexion of the code quality of
|
how errors with this feature were a reflexion of the code quality of
|
||||||
Mayannecessitated the removal of the database conversion feature.
|
Mayan necessitated the removal of the database conversion feature.
|
||||||
|
|
||||||
- Django environ
|
- Django environ
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
__title__ = 'Mayan EDMS'
|
__title__ = 'Mayan EDMS'
|
||||||
__version__ = '3.3beta1'
|
__version__ = '3.3beta1'
|
||||||
__build__ = 0x030208
|
__build__ = 0x030300
|
||||||
__build_string__ = 'v3.2.8-255-g69086d87dd_Tue Oct 8 09:43:10 2019 -0400'
|
__build_string__ = 'v3.3beta1_Sat Oct 5 15:08:53 2019 -0400'
|
||||||
__django_version__ = '1.11'
|
__django_version__ = '1.11'
|
||||||
__author__ = 'Roberto Rosario'
|
__author__ = 'Roberto Rosario'
|
||||||
__author_email__ = 'roberto.rosario@mayan-edms.com'
|
__author_email__ = 'roberto.rosario@mayan-edms.com'
|
||||||
|
|||||||
@@ -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,)
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ class MultiFormView(DjangoFormView):
|
|||||||
template_name = 'appearance/generic_form.html'
|
template_name = 'appearance/generic_form.html'
|
||||||
|
|
||||||
def _create_form(self, form_name, klass):
|
def _create_form(self, form_name, klass):
|
||||||
form_kwargs = self.get_form_kwargs(form_name)
|
form_kwargs = self.get_form_kwargs(form_name=form_name)
|
||||||
form_create_method = 'create_%s_form' % form_name
|
form_create_method = 'create_{}_form'.format(form_name)
|
||||||
if hasattr(self, form_create_method):
|
if hasattr(self, form_create_method):
|
||||||
form = getattr(self, form_create_method)(**form_kwargs)
|
form = getattr(self, form_create_method)(**form_kwargs)
|
||||||
else:
|
else:
|
||||||
@@ -71,7 +71,7 @@ class MultiFormView(DjangoFormView):
|
|||||||
|
|
||||||
def forms_valid(self, forms):
|
def forms_valid(self, forms):
|
||||||
for form_name, form in forms.items():
|
for form_name, form in forms.items():
|
||||||
form_valid_method = '%s_form_valid' % form_name
|
form_valid_method = '{}_form_valid'.format(form_name)
|
||||||
|
|
||||||
if hasattr(self, form_valid_method):
|
if hasattr(self, form_valid_method):
|
||||||
return getattr(self, form_valid_method)(form)
|
return getattr(self, form_valid_method)(form)
|
||||||
@@ -98,8 +98,8 @@ class MultiFormView(DjangoFormView):
|
|||||||
|
|
||||||
def get_form_kwargs(self, form_name):
|
def get_form_kwargs(self, form_name):
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
kwargs.update({'initial': self.get_initial(form_name)})
|
kwargs.update({'initial': self.get_initial(form_name=form_name)})
|
||||||
kwargs.update({'prefix': self.get_prefix(form_name)})
|
kwargs.update({'prefix': self.get_prefix(form_name=form_name)})
|
||||||
|
|
||||||
if self.request.method in ('POST', 'PUT'):
|
if self.request.method in ('POST', 'PUT'):
|
||||||
kwargs.update({
|
kwargs.update({
|
||||||
@@ -124,7 +124,7 @@ class MultiFormView(DjangoFormView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_initial(self, form_name):
|
def get_initial(self, form_name):
|
||||||
initial_method = 'get_%s_initial' % form_name
|
initial_method = 'get_{}_initial'.format(form_name)
|
||||||
if hasattr(self, initial_method):
|
if hasattr(self, initial_method):
|
||||||
return getattr(self, initial_method)()
|
return getattr(self, initial_method)()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -289,8 +404,7 @@ class APIDocumentTypeView(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
'GET': (permission_document_type_view,),
|
'GET': (permission_document_type_view,),
|
||||||
'PUT': (permission_document_type_edit,),
|
'PUT': (permission_document_type_edit,),
|
||||||
'PATCH': (permission_document_type_edit,),
|
'PATCH': (permission_document_type_edit,),
|
||||||
'DELETE': (permission_document_type_delete,)
|
'DELETE': (permission_document_type_delete,) }
|
||||||
}
|
|
||||||
permission_classes = (MayanPermission,)
|
permission_classes = (MayanPermission,)
|
||||||
queryset = DocumentType.objects.all()
|
queryset = DocumentType.objects.all()
|
||||||
|
|
||||||
@@ -423,7 +537,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',
|
||||||
|
|||||||
@@ -43,3 +43,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',),
|
||||||
|
),
|
||||||
|
]
|
||||||
62
mayan/apps/documents/migrations/0054_reset_document_pages.py
Normal file
62
mayan/apps/documents/migrations/0054_reset_document_pages.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
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')
|
||||||
|
DocumentPage = apps.get_model(
|
||||||
|
app_label='documents', model_name='DocumentPage'
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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')
|
||||||
|
DocumentPage = apps.get_model(
|
||||||
|
app_label='documents', model_name='DocumentPage'
|
||||||
|
)
|
||||||
|
|
||||||
|
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():
|
||||||
|
document_page = 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,
|
||||||
@@ -26,7 +28,7 @@ from ..settings import (
|
|||||||
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 +37,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 +60,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 +69,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 +78,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 +95,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 +133,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 +194,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 +220,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 +256,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 +297,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')
|
||||||
|
|||||||
@@ -246,23 +246,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 +274,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,7 +294,7 @@ 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:
|
if setting_fix_orientation.value:
|
||||||
self.fix_orientation()
|
self.fix_orientation()
|
||||||
@@ -337,6 +327,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 +408,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():
|
||||||
|
|||||||
282
mayan/apps/documents/models/document_version_page_models.py
Normal file
282
mayan/apps/documents/models/document_version_page_models.py
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from furl import furl
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.encoding import force_text, python_2_unicode_compatible
|
||||||
|
from django.utils.functional import cached_property
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.converter.literals import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION
|
||||||
|
|
||||||
|
from mayan.apps.converter.models import LayerTransformation
|
||||||
|
from mayan.apps.converter.transformations import (
|
||||||
|
BaseTransformation, TransformationResize, TransformationRotate,
|
||||||
|
TransformationZoom
|
||||||
|
)
|
||||||
|
from mayan.apps.converter.utils import get_converter_class
|
||||||
|
|
||||||
|
from ..managers import DocumentVersionPageManager
|
||||||
|
from ..settings import (
|
||||||
|
setting_disable_base_image_cache, setting_disable_transformed_image_cache,
|
||||||
|
setting_display_width, setting_display_height, setting_zoom_max_level,
|
||||||
|
setting_zoom_min_level
|
||||||
|
)
|
||||||
|
|
||||||
|
from .document_version_models import DocumentVersion
|
||||||
|
|
||||||
|
__all__ = ('DocumentVersionPage', 'DocumentVersionPageResult')
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
class DocumentVersionPage(models.Model):
|
||||||
|
"""
|
||||||
|
Model that describes a document version page
|
||||||
|
"""
|
||||||
|
document_version = models.ForeignKey(
|
||||||
|
on_delete=models.CASCADE, related_name='pages', to=DocumentVersion,
|
||||||
|
verbose_name=_('Document version')
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
|
||||||
|
#def detect_orientation(self):
|
||||||
|
# with self.document_version.open() as file_object:
|
||||||
|
# converter = get_converter_class()(
|
||||||
|
# file_object=file_object,
|
||||||
|
# mime_type=self.document_version.mimetype
|
||||||
|
# )
|
||||||
|
# return converter.detect_orientation(
|
||||||
|
# page_number=self.page_number
|
||||||
|
# )
|
||||||
|
|
||||||
|
@property
|
||||||
|
def document(self):
|
||||||
|
return self.document_version.document
|
||||||
|
|
||||||
|
def generate_image(self, user=None, **kwargs):
|
||||||
|
transformation_list = self.get_combined_transformation_list(user=user, **kwargs)
|
||||||
|
combined_cache_filename = BaseTransformation.combine(transformation_list)
|
||||||
|
|
||||||
|
# Check is transformed image is available
|
||||||
|
logger.debug('transformations cache filename: %s', combined_cache_filename)
|
||||||
|
|
||||||
|
if self.cache_partition.get_file(filename=combined_cache_filename):
|
||||||
|
logger.debug(
|
||||||
|
'transformations cache file "%s" found', combined_cache_filename
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.debug(
|
||||||
|
'transformations cache file "%s" not found', combined_cache_filename
|
||||||
|
)
|
||||||
|
image = self.get_image(transformations=transformation_list)
|
||||||
|
with self.cache_partition.create_file(filename=combined_cache_filename) as file_object:
|
||||||
|
file_object.write(image.getvalue())
|
||||||
|
|
||||||
|
return combined_cache_filename
|
||||||
|
|
||||||
|
#def get_absolute_url(self):
|
||||||
|
# return reverse(
|
||||||
|
# viewname='documents:document_version_page_view', kwargs={
|
||||||
|
# 'pk': self.pk
|
||||||
|
# }
|
||||||
|
# )
|
||||||
|
|
||||||
|
def get_api_image_url(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Create an unique URL combining:
|
||||||
|
- the page's image URL
|
||||||
|
- the interactive argument
|
||||||
|
- a hash from the server side and interactive transformations
|
||||||
|
The purpose of this unique URL is to allow client side caching
|
||||||
|
if document page images.
|
||||||
|
"""
|
||||||
|
transformations_hash = BaseTransformation.combine(
|
||||||
|
self.get_combined_transformation_list(*args, **kwargs)
|
||||||
|
)
|
||||||
|
|
||||||
|
kwargs.pop('transformations', None)
|
||||||
|
|
||||||
|
final_url = furl()
|
||||||
|
final_url.args = kwargs
|
||||||
|
final_url.path = reverse(
|
||||||
|
viewname='rest_api:documentversionpage-image', kwargs={
|
||||||
|
'pk': self.document.pk, 'version_pk': self.document_version.pk,
|
||||||
|
'page_pk': self.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
final_url.args['_hash'] = transformations_hash
|
||||||
|
|
||||||
|
return final_url.tostr()
|
||||||
|
|
||||||
|
def get_combined_transformation_list(self, user=None, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Return a list of transformation containing the server side
|
||||||
|
document page transformation as well as tranformations created
|
||||||
|
from the arguments as transient interactive transformation.
|
||||||
|
"""
|
||||||
|
# Convert arguments into transformations
|
||||||
|
transformations = kwargs.get('transformations', [])
|
||||||
|
|
||||||
|
# Set sensible defaults if the argument is not specified or if the
|
||||||
|
# argument is None
|
||||||
|
width = kwargs.get('width', setting_display_width.value) or setting_display_width.value
|
||||||
|
height = kwargs.get('height', setting_display_height.value) or setting_display_height.value
|
||||||
|
rotation = kwargs.get('rotation', DEFAULT_ROTATION) or DEFAULT_ROTATION
|
||||||
|
zoom_level = kwargs.get('zoom', DEFAULT_ZOOM_LEVEL) or DEFAULT_ZOOM_LEVEL
|
||||||
|
|
||||||
|
if zoom_level < setting_zoom_min_level.value:
|
||||||
|
zoom_level = setting_zoom_min_level.value
|
||||||
|
|
||||||
|
if zoom_level > setting_zoom_max_level.value:
|
||||||
|
zoom_level = setting_zoom_max_level.value
|
||||||
|
|
||||||
|
# Generate transformation hash
|
||||||
|
transformation_list = []
|
||||||
|
|
||||||
|
maximum_layer_order = kwargs.get('maximum_layer_order', None)
|
||||||
|
|
||||||
|
# Stored transformations first
|
||||||
|
for stored_transformation in LayerTransformation.objects.get_for_object(
|
||||||
|
self, maximum_layer_order=maximum_layer_order, as_classes=True,
|
||||||
|
user=user
|
||||||
|
):
|
||||||
|
transformation_list.append(stored_transformation)
|
||||||
|
|
||||||
|
# Interactive transformations second
|
||||||
|
for transformation in transformations:
|
||||||
|
transformation_list.append(transformation)
|
||||||
|
|
||||||
|
if rotation:
|
||||||
|
transformation_list.append(
|
||||||
|
TransformationRotate(degrees=rotation)
|
||||||
|
)
|
||||||
|
|
||||||
|
if width:
|
||||||
|
transformation_list.append(
|
||||||
|
TransformationResize(width=width, height=height)
|
||||||
|
)
|
||||||
|
|
||||||
|
if zoom_level:
|
||||||
|
transformation_list.append(TransformationZoom(percent=zoom_level))
|
||||||
|
|
||||||
|
return transformation_list
|
||||||
|
|
||||||
|
def get_image(self, transformations=None):
|
||||||
|
cache_filename = 'base_image'
|
||||||
|
logger.debug('Page cache filename: %s', cache_filename)
|
||||||
|
|
||||||
|
cache_file = self.cache_partition.get_file(filename=cache_filename)
|
||||||
|
|
||||||
|
if cache_file:
|
||||||
|
logger.debug('Page cache file "%s" found', cache_filename)
|
||||||
|
|
||||||
|
with cache_file.open() as file_object:
|
||||||
|
converter = get_converter_class()(
|
||||||
|
file_object=file_object
|
||||||
|
)
|
||||||
|
|
||||||
|
converter.seek_page(page_number=0)
|
||||||
|
|
||||||
|
# This code is also repeated below to allow using a context
|
||||||
|
# manager with cache_file.open and close it automatically.
|
||||||
|
# Apply runtime transformations
|
||||||
|
for transformation in transformations or []:
|
||||||
|
converter.transform(transformation=transformation)
|
||||||
|
|
||||||
|
return converter.get_page()
|
||||||
|
else:
|
||||||
|
logger.debug('Page cache file "%s" not found', cache_filename)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with self.document_version.get_intermediate_file() as file_object:
|
||||||
|
converter = get_converter_class()(
|
||||||
|
file_object=file_object
|
||||||
|
)
|
||||||
|
converter.seek_page(page_number=self.page_number - 1)
|
||||||
|
|
||||||
|
page_image = converter.get_page()
|
||||||
|
|
||||||
|
# Since open "wb+" doesn't create files, create it explicitly
|
||||||
|
with self.cache_partition.create_file(filename=cache_filename) as file_object:
|
||||||
|
file_object.write(page_image.getvalue())
|
||||||
|
|
||||||
|
# Apply runtime transformations
|
||||||
|
for transformation in transformations or []:
|
||||||
|
converter.transform(transformation=transformation)
|
||||||
|
|
||||||
|
return converter.get_page()
|
||||||
|
except Exception as exception:
|
||||||
|
# Cleanup in case of error
|
||||||
|
logger.error(
|
||||||
|
'Error creating page cache file "%s"; %s',
|
||||||
|
cache_filename, exception
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
#@property
|
||||||
|
#def is_in_trash(self):
|
||||||
|
# return self.document.is_in_trash
|
||||||
|
|
||||||
|
def get_label(self):
|
||||||
|
return _(
|
||||||
|
'Version page %(page_number)d out of %(total_pages)d of %(document)s'
|
||||||
|
) % {
|
||||||
|
'document': force_text(self.document),
|
||||||
|
'page_number': self.page_number,
|
||||||
|
'total_pages': self.document_version.pages.count()
|
||||||
|
}
|
||||||
|
get_label.short_description = _('Label')
|
||||||
|
|
||||||
|
def natural_key(self):
|
||||||
|
return (self.page_number, self.document_version.natural_key())
|
||||||
|
natural_key.dependencies = ['documents.DocumentVersion']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def siblings(self):
|
||||||
|
return DocumentVersionPage.objects.filter(
|
||||||
|
document_version=self.document_version
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def uuid(self):
|
||||||
|
"""
|
||||||
|
Make cache UUID a mix of version ID and page ID to avoid using stale
|
||||||
|
images
|
||||||
|
"""
|
||||||
|
return '{}-{}'.format(self.document_version.uuid, self.pk)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentVersionPageResult(DocumentVersionPage):
|
||||||
|
class Meta:
|
||||||
|
ordering = ('document_version__document', 'page_number')
|
||||||
|
proxy = True
|
||||||
|
verbose_name = _('Document version page')
|
||||||
|
verbose_name_plural = _('Document version pages')
|
||||||
@@ -30,6 +30,10 @@ queue_converter.add_task_type(
|
|||||||
dotted_path='mayan.apps.documents.tasks.task_generate_document_page_image',
|
dotted_path='mayan.apps.documents.tasks.task_generate_document_page_image',
|
||||||
label=_('Generate document page image')
|
label=_('Generate document page image')
|
||||||
)
|
)
|
||||||
|
queue_converter.add_task_type(
|
||||||
|
dotted_path='mayan.apps.documents.tasks.task_generate_document_version_page_image',
|
||||||
|
label=_('Generate document version page image')
|
||||||
|
)
|
||||||
|
|
||||||
queue_documents.add_task_type(
|
queue_documents.add_task_type(
|
||||||
dotted_path='mayan.apps.documents.tasks.task_delete_document',
|
dotted_path='mayan.apps.documents.tasks.task_delete_document',
|
||||||
@@ -66,6 +70,10 @@ queue_tools.add_task_type(
|
|||||||
label=_('Duplicated document scan')
|
label=_('Duplicated document scan')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
queue_uploads.add_task_type(
|
||||||
|
dotted_path='mayan.apps.documents.tasks.task_document_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()
|
||||||
|
|||||||
@@ -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,8 +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_DOCUMENT_RETRY_DELAY,
|
RETRY_DELAY_DOCUMENT_RESET_PAGES, UPDATE_PAGE_COUNT_RETRY_DELAY,
|
||||||
UPLOAD_NEW_VERSION_RETRY_DELAY
|
UPLOAD_NEW_DOCUMENT_RETRY_DELAY,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -65,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(
|
||||||
@@ -81,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(
|
||||||
@@ -177,7 +212,7 @@ def task_upload_new_document(self, document_type_id, shared_uploaded_file_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'
|
||||||
)
|
)
|
||||||
@@ -212,7 +247,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,8 +20,8 @@ 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, DocumentVersionPage
|
||||||
from ..permissions import permission_document_edit, permission_document_view
|
from ..permissions import permission_document_edit, permission_document_view
|
||||||
from ..settings import (
|
from ..settings import (
|
||||||
setting_rotation_step, setting_zoom_percent_step, setting_zoom_max_level,
|
setting_rotation_step, setting_zoom_percent_step, setting_zoom_max_level,
|
||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
queryset = self.get_queryset().filter(search_query.query).distinct()
|
try:
|
||||||
|
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(
|
||||||
|
|||||||
@@ -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:
|
with document_version_page.cache_partition.get_file(filename=cache_filename).open() as file_object:
|
||||||
document_page_content, created = DocumentPageOCRContent.objects.get_or_create(
|
document_version_page_content, created = DocumentVersionPageOCRContent.objects.get_or_create(
|
||||||
document_page=document_page
|
document_version_page=document_version_page
|
||||||
)
|
)
|
||||||
document_page_content.content = ocr_backend.execute(
|
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
|
||||||
|
|||||||
42
mayan/apps/ocr/migrations/0009_rename_page_content.py
Normal file
42
mayan/apps/ocr/migrations/0009_rename_page_content.py
Normal file
@@ -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 = [
|
||||||
|
('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,
|
||||||
|
#name='document_version_page',
|
||||||
|
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 (
|
||||||
|
DocumentPage, 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,
|
||||||
@@ -145,10 +146,16 @@ class SourcesApp(MayanAppConfig):
|
|||||||
menu_secondary.bind_links(
|
menu_secondary.bind_links(
|
||||||
links=(link_document_version_upload,),
|
links=(link_document_version_upload,),
|
||||||
sources=(
|
sources=(
|
||||||
'documents:document_version_list', 'documents:upload_version',
|
'documents:document_version_list', 'sources:upload_version',
|
||||||
'documents:document_version_revert'
|
'documents:document_version_revert'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
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,
|
||||||
|
|||||||
@@ -23,14 +23,25 @@ class NewDocumentForm(DocumentForm):
|
|||||||
|
|
||||||
|
|
||||||
class NewVersionForm(forms.Form):
|
class NewVersionForm(forms.Form):
|
||||||
def __init__(self, *args, **kwargs):
|
comment = forms.CharField(
|
||||||
super(NewVersionForm, self).__init__(*args, **kwargs)
|
help_text=_('An optional comment to explain the upload.'),
|
||||||
|
label=_('Comment'), required=False,
|
||||||
|
widget=forms.widgets.Textarea(attrs={'rows': 4}),
|
||||||
|
)
|
||||||
|
|
||||||
self.fields['comment'] = forms.CharField(
|
append_pages = forms.BooleanField(
|
||||||
label=_('Comment'),
|
help_text=_(
|
||||||
required=False,
|
'If selected, the pages of the file uploaded will be appended '
|
||||||
widget=forms.widgets.Textarea(attrs={'rows': 4}),
|
'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',
|
||||||
|
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',
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ from .api_views import (
|
|||||||
APIStagingSourceListView, APIStagingSourceView
|
APIStagingSourceListView, APIStagingSourceView
|
||||||
)
|
)
|
||||||
from .views import (
|
from .views import (
|
||||||
SetupSourceCheckView, SetupSourceCreateView, SetupSourceDeleteView,
|
DocumentPagesAppendView, SetupSourceCheckView, SetupSourceCreateView,
|
||||||
SetupSourceEditView, SetupSourceListView, SourceLogListView,
|
SetupSourceDeleteView, SetupSourceEditView, SetupSourceListView,
|
||||||
StagingFileDeleteView, UploadInteractiveVersionView, UploadInteractiveView
|
SourceLogListView, StagingFileDeleteView, UploadInteractiveVersionView,
|
||||||
|
UploadInteractiveView
|
||||||
)
|
)
|
||||||
from .wizards import DocumentCreateWizard
|
from .wizards import DocumentCreateWizard
|
||||||
|
|
||||||
@@ -41,6 +42,14 @@ urlpatterns = [
|
|||||||
regex=r'^documents/(?P<document_pk>\d+)/versions/upload/interactive/$',
|
regex=r'^documents/(?P<document_pk>\d+)/versions/upload/interactive/$',
|
||||||
view=UploadInteractiveVersionView.as_view(), name='upload_version'
|
view=UploadInteractiveVersionView.as_view(), name='upload_version'
|
||||||
),
|
),
|
||||||
|
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 UploadInteractiveVersionView(UploadBaseView):
|
class UploadInteractiveVersionView(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 UploadInteractiveVersionView(UploadBaseView):
|
|||||||
else:
|
else:
|
||||||
user_id = None
|
user_id = None
|
||||||
|
|
||||||
task_upload_new_version.apply_async(kwargs=dict(
|
task_upload_new_version.apply_async(
|
||||||
shared_uploaded_file_id=shared_uploaded_file.pk,
|
kwargs=dict(
|
||||||
document_id=self.document.pk,
|
append_pages=forms['document_form'].cleaned_data.get(
|
||||||
user_id=user_id,
|
'append_pages', False
|
||||||
comment=forms['document_form'].cleaned_data.get('comment')
|
),
|
||||||
))
|
shared_uploaded_file_id=shared_uploaded_file.pk,
|
||||||
|
document_id=self.document.pk,
|
||||||
|
user_id=user_id,
|
||||||
|
comment=forms['document_form'].cleaned_data.get('comment')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
messages.success(
|
messages.success(
|
||||||
message=_(
|
message=_(
|
||||||
@@ -448,13 +452,6 @@ class UploadInteractiveVersionView(UploadBaseView):
|
|||||||
files=kwargs.get('files', None),
|
files=kwargs.get('files', None),
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_document_form_form(self, **kwargs):
|
|
||||||
return self.get_form_classes()['document_form'](
|
|
||||||
prefix=kwargs['prefix'],
|
|
||||||
data=kwargs.get('data', None),
|
|
||||||
files=kwargs.get('files', None),
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_form_classes(self):
|
def get_form_classes(self):
|
||||||
return {
|
return {
|
||||||
'document_form': NewVersionForm,
|
'document_form': NewVersionForm,
|
||||||
@@ -467,8 +464,33 @@ class UploadInteractiveVersionView(UploadBaseView):
|
|||||||
).get_context_data(**kwargs)
|
).get_context_data(**kwargs)
|
||||||
context['object'] = self.document
|
context['object'] = self.document
|
||||||
context['title'] = _(
|
context['title'] = _(
|
||||||
'Upload a new version from source: %s'
|
'Upload a new version for document "%(document)s" from source: %(source)s'
|
||||||
) % self.source.label
|
) % {'document': self.document, 'source': self.source.label}
|
||||||
|
|
||||||
|
context['submit_label'] = _('Submit')
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentPagesAppendView(UploadInteractiveVersionView):
|
||||||
|
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,)
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
Pillow==6.0.0
|
Pillow==6.0.0
|
||||||
PyPDF2==1.26.0
|
PyPDF2==1.26.0
|
||||||
PyYAML==5.1.1
|
PyYAML==5.1.1
|
||||||
celery==4.3.0
|
|
||||||
django-activity-stream==0.7.0
|
django-activity-stream==0.7.0
|
||||||
django-celery-beat==1.5.0
|
|
||||||
django-colorful==1.3
|
django-colorful==1.3
|
||||||
django-cors-headers==2.5.2
|
django-cors-headers==2.5.2
|
||||||
django-downloadview==1.9
|
django-downloadview==1.9
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -60,9 +60,7 @@ django==1.11.24
|
|||||||
Pillow==6.0.0
|
Pillow==6.0.0
|
||||||
PyPDF2==1.26.0
|
PyPDF2==1.26.0
|
||||||
PyYAML==5.1.1
|
PyYAML==5.1.1
|
||||||
celery==4.3.0
|
|
||||||
django-activity-stream==0.7.0
|
django-activity-stream==0.7.0
|
||||||
django-celery-beat==1.5.0
|
|
||||||
django-colorful==1.3
|
django-colorful==1.3
|
||||||
django-cors-headers==2.5.2
|
django-cors-headers==2.5.2
|
||||||
django-downloadview==1.9
|
django-downloadview==1.9
|
||||||
|
|||||||
Reference in New Issue
Block a user