Update the file caching app
- Add view to list available caches. - Add links to view and purge caches. - Add permissions. - Add events. - Add purge task. - Remove document image clear link and view. This is now handled by the file caching app. Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
This commit is contained in:
@@ -61,6 +61,8 @@
|
||||
regardless of MIME type.
|
||||
- Remove task inspection from task manager app.
|
||||
- Move pagination navigation inside the toolbar.
|
||||
- Remove document image clear link and view.
|
||||
This is now handled by the file caching app.
|
||||
|
||||
3.2.6 (2019-07-10)
|
||||
==================
|
||||
|
||||
@@ -76,6 +76,8 @@ Changes
|
||||
- Add platform template to return queues for a worker.
|
||||
- Remove task inspection from task manager app.
|
||||
- Move pagination navigation inside the toolbar.
|
||||
- Remove document image clear link and view.
|
||||
This is now handled by the file caching app.
|
||||
|
||||
|
||||
Removals
|
||||
|
||||
@@ -47,7 +47,7 @@ from .handlers import (
|
||||
handler_remove_empty_duplicates_lists, handler_scan_duplicates_for
|
||||
)
|
||||
from .links import (
|
||||
link_clear_image_cache, link_document_clear_transformations,
|
||||
link_document_clear_transformations,
|
||||
link_document_clone_transformations, link_document_delete,
|
||||
link_document_document_type_edit, link_document_download,
|
||||
link_document_duplicates_list, link_document_edit,
|
||||
@@ -377,7 +377,7 @@ class DocumentsApp(MayanAppConfig):
|
||||
|
||||
menu_setup.bind_links(links=(link_document_type_setup,))
|
||||
menu_tools.bind_links(
|
||||
links=(link_clear_image_cache, link_duplicated_document_scan)
|
||||
links=(link_duplicated_document_scan,)
|
||||
)
|
||||
|
||||
# Document type links
|
||||
|
||||
@@ -12,8 +12,6 @@ icon_document_type = Icon(
|
||||
|
||||
icon_menu_documents = Icon(driver_name='fontawesome', symbol='book')
|
||||
|
||||
icon_clear_image_cache = Icon(driver_name='fontawesome', symbol='file-image')
|
||||
|
||||
icon_dashboard_document_types = icon_document_type
|
||||
icon_dashboard_documents_in_trash = Icon(
|
||||
driver_name='fontawesome', symbol='trash-alt'
|
||||
|
||||
@@ -8,7 +8,7 @@ from mayan.apps.converter.permissions import (
|
||||
from mayan.apps.navigation.classes import Link
|
||||
|
||||
from .icons import (
|
||||
icon_clear_image_cache, icon_document_list_recent_access,
|
||||
icon_document_list_recent_access,
|
||||
icon_recent_added_document_list, icon_document_page_navigation_first,
|
||||
icon_document_page_navigation_last, icon_document_page_navigation_next,
|
||||
icon_document_page_navigation_previous, icon_document_page_return,
|
||||
@@ -264,16 +264,6 @@ link_document_list_deleted = Link(
|
||||
text=_('Trash can'), view='documents:document_list_deleted'
|
||||
)
|
||||
|
||||
# Tools
|
||||
link_clear_image_cache = Link(
|
||||
icon_class=icon_clear_image_cache,
|
||||
description=_(
|
||||
'Clear the graphics representations used to speed up the documents\' '
|
||||
'display and interactive transformations results.'
|
||||
), permissions=(permission_document_tools,),
|
||||
text=_('Clear document image cache'),
|
||||
view='documents:document_clear_image_cache'
|
||||
)
|
||||
link_trash_can_empty = Link(
|
||||
permissions=(permission_empty_trash,), text=_('Empty trash'),
|
||||
view='documents:trash_can_empty'
|
||||
|
||||
@@ -61,10 +61,6 @@ queue_documents_periodic.add_task_type(
|
||||
schedule=timedelta(seconds=DELETE_STALE_STUBS_INTERVAL),
|
||||
)
|
||||
|
||||
queue_tools.add_task_type(
|
||||
dotted_path='mayan.apps.documents.tasks.task_clear_image_cache',
|
||||
label=_('Clear image cache')
|
||||
)
|
||||
queue_tools.add_task_type(
|
||||
dotted_path='mayan.apps.documents.tasks.task_scan_duplicates_all',
|
||||
label=_('Duplicated document scan')
|
||||
|
||||
@@ -41,17 +41,6 @@ def task_check_trash_periods():
|
||||
DocumentType.objects.check_trash_periods()
|
||||
|
||||
|
||||
@app.task(ignore_result=True)
|
||||
def task_clear_image_cache():
|
||||
Document = apps.get_model(
|
||||
app_label='documents', model_name='Document'
|
||||
)
|
||||
|
||||
logger.info('Starting document cache invalidation')
|
||||
Document.objects.invalidate_cache()
|
||||
logger.info('Finished document cache invalidation')
|
||||
|
||||
|
||||
@app.task(ignore_result=True)
|
||||
def task_delete_document(trashed_document_id):
|
||||
DeletedDocument = apps.get_model(
|
||||
|
||||
@@ -13,7 +13,7 @@ from .api_views import (
|
||||
APIRecentDocumentListView
|
||||
)
|
||||
from .views import (
|
||||
ClearImageCacheView, DocumentDocumentTypeEditView, DocumentDownloadFormView,
|
||||
DocumentDocumentTypeEditView, DocumentDownloadFormView,
|
||||
DocumentDownloadView, DocumentDuplicatesListView, DocumentEditView,
|
||||
DocumentListView, DocumentPageListView, DocumentPageNavigationFirst,
|
||||
DocumentPageNavigationLast, DocumentPageNavigationNext,
|
||||
@@ -272,10 +272,6 @@ urlpatterns = [
|
||||
view=DocumentTransformationsClearView.as_view(),
|
||||
name='document_multiple_clear_transformations'
|
||||
),
|
||||
url(
|
||||
regex=r'^cache/clear/$', view=ClearImageCacheView.as_view(),
|
||||
name='document_clear_image_cache'
|
||||
),
|
||||
url(
|
||||
regex=r'^page/(?P<pk>\d+)/$', view=DocumentPageView.as_view(),
|
||||
name='document_page_view'
|
||||
|
||||
@@ -8,25 +8,12 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from mayan.apps.common.generics import ConfirmView
|
||||
|
||||
from ..permissions import permission_document_tools
|
||||
from ..tasks import task_clear_image_cache, task_scan_duplicates_all
|
||||
from ..tasks import task_scan_duplicates_all
|
||||
|
||||
__all__ = ('ClearImageCacheView', 'ScanDuplicatedDocuments')
|
||||
__all__ = ('ScanDuplicatedDocuments',)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClearImageCacheView(ConfirmView):
|
||||
extra_context = {
|
||||
'title': _('Clear the document image cache?')
|
||||
}
|
||||
view_permission = permission_document_tools
|
||||
|
||||
def view_action(self):
|
||||
task_clear_image_cache.apply_async()
|
||||
messages.success(
|
||||
self.request, _('Document cache clearing queued successfully.')
|
||||
)
|
||||
|
||||
|
||||
class ScanDuplicatedDocuments(ConfirmView):
|
||||
extra_context = {
|
||||
'title': _('Scan for duplicated documents?')
|
||||
|
||||
@@ -1,8 +1,79 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.acls.classes import ModelPermission
|
||||
from mayan.apps.acls.links import link_acl_list
|
||||
from mayan.apps.acls.permissions import permission_acl_edit, permission_acl_view
|
||||
from mayan.apps.common.apps import MayanAppConfig
|
||||
from mayan.apps.common.menus import (
|
||||
menu_list_facet, menu_multi_item, menu_object, menu_secondary, menu_tools
|
||||
)
|
||||
from mayan.apps.events.classes import ModelEventType
|
||||
from mayan.apps.events.links import (
|
||||
link_events_for_object, link_object_event_types_user_subcriptions_list
|
||||
)
|
||||
from mayan.apps.navigation.classes import SourceColumn
|
||||
|
||||
from .events import event_cache_edited, event_cache_purged
|
||||
from .links import (
|
||||
link_caches_list, link_cache_multiple_purge, link_cache_purge
|
||||
)
|
||||
from .permissions import permission_cache_purge, permission_cache_view
|
||||
|
||||
|
||||
class FileCachingConfig(MayanAppConfig):
|
||||
app_namespace = 'file_caching'
|
||||
app_url = 'file_caching'
|
||||
has_tests = False
|
||||
name = 'mayan.apps.file_caching'
|
||||
verbose_name = _('File caching')
|
||||
|
||||
def ready(self):
|
||||
super(FileCachingConfig, self).ready()
|
||||
from actstream import registry
|
||||
|
||||
Cache = self.get_model(model_name='Cache')
|
||||
|
||||
ModelEventType.register(
|
||||
event_types=(event_cache_edited, event_cache_purged,),
|
||||
model=Cache
|
||||
)
|
||||
|
||||
ModelPermission.register(
|
||||
model=Cache, permissions=(
|
||||
permission_acl_edit, permission_acl_view,
|
||||
permission_cache_purge, permission_cache_view
|
||||
)
|
||||
)
|
||||
|
||||
SourceColumn(attribute='name', source=Cache)
|
||||
SourceColumn(attribute='label', source=Cache)
|
||||
SourceColumn(attribute='storage_instance_path', source=Cache)
|
||||
SourceColumn(attribute='get_maximum_size_display', source=Cache)
|
||||
SourceColumn(attribute='get_total_size_display', source=Cache)
|
||||
|
||||
menu_list_facet.bind_links(
|
||||
links=(
|
||||
link_acl_list, link_events_for_object,
|
||||
link_object_event_types_user_subcriptions_list,
|
||||
), sources=(Cache,)
|
||||
)
|
||||
|
||||
menu_object.bind_links(
|
||||
links=(link_cache_purge,),
|
||||
sources=(Cache,)
|
||||
)
|
||||
menu_multi_item.bind_links(
|
||||
links=(link_cache_multiple_purge,),
|
||||
sources=(Cache,)
|
||||
)
|
||||
menu_secondary.bind_links(
|
||||
links=(link_caches_list,), sources=(
|
||||
Cache,
|
||||
)
|
||||
)
|
||||
|
||||
menu_tools.bind_links(links=(link_caches_list,))
|
||||
|
||||
registry.register(Cache)
|
||||
|
||||
19
mayan/apps/file_caching/events.py
Normal file
19
mayan/apps/file_caching/events.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.events.classes import EventTypeNamespace
|
||||
|
||||
namespace = EventTypeNamespace(
|
||||
label=_('File caching'), name='file_caching'
|
||||
)
|
||||
|
||||
event_cache_created = namespace.add_event_type(
|
||||
label=_('Cache created'), name='cache_created'
|
||||
)
|
||||
event_cache_edited = namespace.add_event_type(
|
||||
label=_('Cache edited'), name='cache_edited'
|
||||
)
|
||||
event_cache_purged = namespace.add_event_type(
|
||||
label=_('Cache purge'), name='cache_purged'
|
||||
)
|
||||
11
mayan/apps/file_caching/icons.py
Normal file
11
mayan/apps/file_caching/icons.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from mayan.apps.appearance.classes import Icon
|
||||
|
||||
icon_file_caching = Icon(
|
||||
driver_name='fontawesome', symbol='warehouse'
|
||||
)
|
||||
icon_cache_purge = Icon(
|
||||
driver_name='fontawesome-dual', primary_symbol='warehouse',
|
||||
secondary_symbol='check'
|
||||
)
|
||||
22
mayan/apps/file_caching/links.py
Normal file
22
mayan/apps/file_caching/links.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.navigation.classes import Link
|
||||
|
||||
from .icons import icon_cache_purge, icon_file_caching
|
||||
from .permissions import permission_cache_purge, permission_cache_view
|
||||
|
||||
link_caches_list = Link(
|
||||
icon_class=icon_file_caching, permissions=(permission_cache_view,),
|
||||
text=_('File caches'), view='file_caching:cache_list'
|
||||
)
|
||||
link_cache_purge = Link(
|
||||
icon_class=icon_cache_purge, kwargs={'cache_id': 'resolved_object.id'},
|
||||
permissions=(permission_cache_purge,), text=_('Purge cache'),
|
||||
view='file_caching:cache_purge'
|
||||
)
|
||||
link_cache_multiple_purge = Link(
|
||||
icon_class=icon_cache_purge, text=_('Purge cache'),
|
||||
view='file_caching:cache_multiple_purge'
|
||||
)
|
||||
@@ -6,6 +6,7 @@ import logging
|
||||
from django.core.files.base import ContentFile
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Sum
|
||||
from django.template.defaultfilters import filesizeformat
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.module_loading import import_string
|
||||
@@ -14,18 +15,31 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from mayan.apps.lock_manager.exceptions import LockError
|
||||
from mayan.apps.lock_manager.runtime import locking_backend
|
||||
|
||||
from .events import (
|
||||
event_cache_created, event_cache_edited, event_cache_purged
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Cache(models.Model):
|
||||
name = models.CharField(
|
||||
max_length=128, unique=True, verbose_name=_('Name')
|
||||
help_text=_('Internal name of the cache.'), max_length=128,
|
||||
unique=True, verbose_name=_('Name')
|
||||
)
|
||||
label = models.CharField(
|
||||
help_text=_('A short text describing the cache.'), max_length=128,
|
||||
verbose_name=_('Label')
|
||||
)
|
||||
maximum_size = models.PositiveIntegerField(
|
||||
help_text=_('Maximum size of the cache in bytes.'),
|
||||
verbose_name=_('Maximum size')
|
||||
)
|
||||
label = models.CharField(max_length=128, verbose_name=_('Label'))
|
||||
maximum_size = models.PositiveIntegerField(verbose_name=_('Maximum size'))
|
||||
storage_instance_path = models.CharField(
|
||||
max_length=255, unique=True, verbose_name=_('Storage instance path')
|
||||
help_text=_(
|
||||
'Dotted path to the actual storage class used for the cache.'
|
||||
), max_length=255, unique=True, verbose_name=_('Storage instance path')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -38,21 +52,55 @@ class Cache(models.Model):
|
||||
def get_files(self):
|
||||
return CachePartitionFile.objects.filter(partition__cache__id=self.pk)
|
||||
|
||||
def get_maximum_size_display(self):
|
||||
return filesizeformat(bytes_=self.maximum_size)
|
||||
|
||||
get_maximum_size_display.short_description = _('Maximum size')
|
||||
|
||||
def get_total_size(self):
|
||||
"""
|
||||
Return the actual usage of the cache.
|
||||
"""
|
||||
return self.get_files().aggregate(
|
||||
file_size__sum=Sum('file_size')
|
||||
)['file_size__sum'] or 0
|
||||
|
||||
def get_total_size_display(self):
|
||||
return filesizeformat(bytes_=self.get_total_size())
|
||||
|
||||
get_total_size_display.short_description = _('Total size')
|
||||
|
||||
def prune(self):
|
||||
"""
|
||||
Deletes files until the total size of the cache is below the allowed
|
||||
maximum size of the cache.
|
||||
"""
|
||||
while self.get_total_size() > self.maximum_size:
|
||||
self.get_files().earliest().delete()
|
||||
|
||||
def purge(self):
|
||||
def purge(self, _user=None):
|
||||
"""
|
||||
Deletes the entire cache.
|
||||
"""
|
||||
for partition in self.partitions.all():
|
||||
partition.purge()
|
||||
|
||||
event_cache_purged.commit(actor=_user, target=self)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
result = super(Cache, self).save(*args, **kwargs)
|
||||
_user = kwargs.pop('_user', None)
|
||||
with transaction.atomic():
|
||||
is_new = not self.pk
|
||||
result = super(Cache, self).save(*args, **kwargs)
|
||||
if is_new:
|
||||
event_cache_created.commit(
|
||||
actor=_user, target=self
|
||||
)
|
||||
else:
|
||||
event_cache_edited.commit(
|
||||
actor=_user, target=self
|
||||
)
|
||||
|
||||
self.prune()
|
||||
return result
|
||||
|
||||
|
||||
14
mayan/apps/file_caching/permissions.py
Normal file
14
mayan/apps/file_caching/permissions.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.permissions import PermissionNamespace
|
||||
|
||||
namespace = PermissionNamespace(label=_('File caching'), name='file_caching')
|
||||
|
||||
permission_cache_purge = namespace.add_permission(
|
||||
label=_('Purge a file cache'), name='file_caching_cache_purge'
|
||||
)
|
||||
permission_cache_view = namespace.add_permission(
|
||||
label=_('View a file cache'), name='file_caching_cache_view'
|
||||
)
|
||||
10
mayan/apps/file_caching/queues.py
Normal file
10
mayan/apps/file_caching/queues.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.common.queues import queue_tools
|
||||
|
||||
queue_tools.add_task_type(
|
||||
dotted_path='mayan.apps.file_caching.tasks.task_cache_purge',
|
||||
label=_('Purge a file cache')
|
||||
)
|
||||
25
mayan/apps/file_caching/tasks.py
Normal file
25
mayan/apps/file_caching/tasks.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from django.apps import apps
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from mayan.celery import app
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@app.task(ignore_result=True)
|
||||
def task_cache_purge(cache_id, user_id=None):
|
||||
Cache = apps.get_model(
|
||||
app_label='file_caching', model_name='Cache'
|
||||
)
|
||||
User = get_user_model()
|
||||
|
||||
cache = Cache.objects.get(pk=cache_id)
|
||||
user = User.objects.get(pk=user_id)
|
||||
|
||||
logger.info('Starting cache id %s purge', cache)
|
||||
cache.purge(_user=user)
|
||||
logger.info('Finished cache id %s purge', cache)
|
||||
20
mayan/apps/file_caching/urls.py
Normal file
20
mayan/apps/file_caching/urls.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from .views import CacheListView, CachePurgeView
|
||||
|
||||
urlpatterns = [
|
||||
url(
|
||||
regex=r'^caches/$',
|
||||
name='cache_list', view=CacheListView.as_view()
|
||||
),
|
||||
url(
|
||||
regex=r'^caches/(?P<cache_id>\d+)/purge/$',
|
||||
name='cache_purge', view=CachePurgeView.as_view()
|
||||
),
|
||||
url(
|
||||
regex=r'^caches/multiple/purge/$', name='cache_multiple_purge',
|
||||
view=CachePurgeView.as_view()
|
||||
),
|
||||
]
|
||||
53
mayan/apps/file_caching/views.py
Normal file
53
mayan/apps/file_caching/views.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext
|
||||
|
||||
from mayan.apps.common.generics import (
|
||||
MultipleObjectConfirmActionView, SingleObjectListView
|
||||
)
|
||||
|
||||
from .models import Cache
|
||||
from .permissions import permission_cache_purge, permission_cache_view
|
||||
|
||||
from .tasks import task_cache_purge
|
||||
|
||||
|
||||
class CacheListView(SingleObjectListView):
|
||||
model = Cache
|
||||
permission = permission_cache_view
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'hide_object': True,
|
||||
'title': _('File caches list')
|
||||
}
|
||||
|
||||
|
||||
class CachePurgeView(MultipleObjectConfirmActionView):
|
||||
model = Cache
|
||||
object_permission = permission_cache_purge
|
||||
pk_url_kwarg = 'cache_id'
|
||||
success_message_singular = '%(count)d cache submitted for purging.'
|
||||
success_message_plural = '%(count)d caches submitted for purging.'
|
||||
|
||||
def get_extra_context(self):
|
||||
queryset = self.object_list
|
||||
|
||||
result = {
|
||||
'title': ungettext(
|
||||
singular='Submit the selected cache for purging?',
|
||||
plural='Submit the selected caches for purging?',
|
||||
number=queryset.count()
|
||||
)
|
||||
}
|
||||
|
||||
if queryset.count() == 1:
|
||||
result['object'] = queryset.first()
|
||||
|
||||
return result
|
||||
|
||||
def object_action(self, form, instance):
|
||||
task_cache_purge.apply_async(
|
||||
kwargs={'cache_id': instance.pk, 'user_id': self.request.user.pk}
|
||||
)
|
||||
Reference in New Issue
Block a user