diff --git a/HISTORY.rst b/HISTORY.rst
index ff16063202..26b4be0f83 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -211,6 +211,9 @@
clicking on the title of the body of the card, not just on
the checkbox next to the title.
- Event handler to highlight panels when selected.
+- Improve duplicated document display.
+- Filter document duplicted count by access.
+
3.1.9 (2018-11-01)
==================
diff --git a/mayan/apps/appearance/templates/appearance/generic_list_items_subtemplate.html b/mayan/apps/appearance/templates/appearance/generic_list_items_subtemplate.html
index 96cb181ba8..cbc13af478 100644
--- a/mayan/apps/appearance/templates/appearance/generic_list_items_subtemplate.html
+++ b/mayan/apps/appearance/templates/appearance/generic_list_items_subtemplate.html
@@ -76,7 +76,7 @@
{{ object }}
{% endif %}
{% else %}
- {% source_column_resolve column=object|get_source_columns:"only_identifier" %}
+ {% source_column_resolve column=object|get_source_columns:"only_identifier" as column_value %}{{ column_value }}
{% endif %}
diff --git a/mayan/apps/appearance/templates/appearance/generic_list_subtemplate.html b/mayan/apps/appearance/templates/appearance/generic_list_subtemplate.html
index 67e72586e8..6be960e853 100644
--- a/mayan/apps/appearance/templates/appearance/generic_list_subtemplate.html
+++ b/mayan/apps/appearance/templates/appearance/generic_list_subtemplate.html
@@ -98,7 +98,7 @@
{% endif %}
{% if not hide_columns %}
{% for column in object|get_source_columns %}
-
{% source_column_resolve column=column %}{{ column_result }} |
+ {% source_column_resolve column=column as column_value %}{{ column_value }} | {# Use explicit 'as column_value ' to force date rendering #}
{% endfor %}
{% endif %}
{% for column in extra_columns %}
diff --git a/mayan/apps/common/generics.py b/mayan/apps/common/generics.py
index 66b4fcc9b1..c9b57e4123 100644
--- a/mayan/apps/common/generics.py
+++ b/mayan/apps/common/generics.py
@@ -23,6 +23,10 @@ from pure_pagination.mixins import PaginationMixin
from .forms import ChoiceForm
from .icons import icon_assign_remove_add, icon_assign_remove_remove
+from .literals import (
+ TEXT_CHOICE_ITEMS, TEXT_CHOICE_LIST, TEXT_LIST_AS_ITEMS,
+ TEXT_LIST_AS_ITEMS_PARAMETER
+)
from .mixins import (
DeleteExtraDataMixin, DynamicFormViewMixin, ExtraContextMixin,
FormExtraKwargsMixin, MultipleObjectMixin, ObjectActionMixin,
@@ -500,14 +504,17 @@ class SingleObjectListView(PaginationMixin, ViewPermissionCheckMixin, ObjectList
def get_context_data(self, **kwargs):
context = super(SingleObjectListView, self).get_context_data(**kwargs)
- if context.get('list_as_items'):
- default_mode = 'items'
+
+ if context.get(TEXT_LIST_AS_ITEMS):
+ default_mode = TEXT_CHOICE_ITEMS
else:
- default_mode = 'list'
+ default_mode = TEXT_CHOICE_LIST
- list_mode = self.request.GET.get('_list_mode', default_mode)
+ list_mode = self.request.GET.get(
+ TEXT_LIST_AS_ITEMS_PARAMETER, default_mode
+ )
- context.update({'list_as_items': list_mode=='items'})
+ context.update({TEXT_LIST_AS_ITEMS: list_mode == TEXT_CHOICE_ITEMS})
return context
def get_paginate_by(self, queryset):
diff --git a/mayan/apps/common/icons.py b/mayan/apps/common/icons.py
index a1e6c3e960..cab1d89260 100644
--- a/mayan/apps/common/icons.py
+++ b/mayan/apps/common/icons.py
@@ -30,8 +30,8 @@ icon_menu_about = Icon(
icon_menu_user = Icon(
driver_name='fontawesome', symbol='user-circle'
)
-icon_object_error_list_with_icon = Icon(
- driver_name='fontawesome', symbol='lock'
+icon_object_error_list = Icon(
+ driver_name='fontawesome', symbol='exclamation-triangle'
)
icon_ok = Icon(
driver_name='fontawesome', symbol='check'
diff --git a/mayan/apps/common/links.py b/mayan/apps/common/links.py
index f49d4affb0..642cb54f2b 100644
--- a/mayan/apps/common/links.py
+++ b/mayan/apps/common/links.py
@@ -10,7 +10,7 @@ from .icons import (
icon_about, icon_check_version, icon_current_user_details,
icon_current_user_edit, icon_current_user_locale_profile_details,
icon_current_user_locale_profile_edit, icon_documentation, icon_forum,
- icon_license, icon_object_error_list_with_icon, icon_packages_licenses,
+ icon_license, icon_object_error_list, icon_packages_licenses,
icon_setup, icon_source_code, icon_support, icon_tools
)
from .permissions_runtime import permission_error_log_view
@@ -65,6 +65,7 @@ link_documentation = Link(
text=_('Documentation'), url='https://docs.mayan-edms.com'
)
link_object_error_list = Link(
+ icon_class=icon_object_error_list,
kwargs=get_kwargs_factory('resolved_object'),
permissions=(permission_error_log_view,), text=_('Errors'),
view='common:object_error_list',
@@ -74,12 +75,6 @@ link_object_error_list_clear = Link(
permissions=(permission_error_log_view,), text=_('Clear all'),
view='common:object_error_list_clear',
)
-link_object_error_list_with_icon = Link(
- kwargs=get_kwargs_factory('resolved_object'),
- icon_class=icon_object_error_list_with_icon,
- permissions=(permission_error_log_view,), text=_('Errors'),
- view='common:error_list',
-)
link_forum = Link(
icon_class=icon_forum, tags='new_window', text=_('Forum'),
url='https://forum.mayan-edms.com'
diff --git a/mayan/apps/common/literals.py b/mayan/apps/common/literals.py
index 7513a2d33c..f2d9f2ecce 100644
--- a/mayan/apps/common/literals.py
+++ b/mayan/apps/common/literals.py
@@ -11,6 +11,12 @@ MESSAGE_SQLITE_WARNING = _(
'for development and testing, not for production.'
)
PYPI_URL = 'https://pypi.python.org/pypi'
+
+TEXT_LIST_AS_ITEMS_PARAMETER = '_list_mode'
+TEXT_LIST_AS_ITEMS = 'list_as_items'
+TEXT_CHOICE_ITEMS = 'items'
+TEXT_CHOICE_LIST = 'list'
+
TIME_DELTA_UNIT_DAYS = 'days'
TIME_DELTA_UNIT_HOURS = 'hours'
TIME_DELTA_UNIT_MINUTES = 'minutes'
diff --git a/mayan/apps/common/utils.py b/mayan/apps/common/utils.py
index e26fa28575..c720f8f3d7 100644
--- a/mayan/apps/common/utils.py
+++ b/mayan/apps/common/utils.py
@@ -4,7 +4,6 @@ import logging
import os
import shutil
import tempfile
-import types
from django.conf import settings
from django.db.models.constants import LOOKUP_SEP
@@ -18,7 +17,6 @@ from django.utils.six.moves import xmlrpc_client
from django.utils.translation import ugettext_lazy as _
import mayan
-from mayan.apps.common.compat import dict_type, dictionary_type
from .exceptions import NotLatestVersion, UnknownLatestVersion
from .literals import DJANGO_SQLITE_BACKEND, MAYAN_PYPI_NAME, PYPI_URL
@@ -155,9 +153,9 @@ def resolve_attribute(obj, attribute, kwargs=None):
except AttributeError:
# Try as a related model field
if LOOKUP_SEP in attribute:
- attrib = attribute.replace(LOOKUP_SEP, '.')
+ attribute_replaced = attribute.replace(LOOKUP_SEP, '.')
return resolve_attribute(
- obj=obj, attribute=attribute, kwargs=kwargs
+ obj=obj, attribute=attribute_replaced, kwargs=kwargs
)
else:
raise
diff --git a/mayan/apps/common/views.py b/mayan/apps/common/views.py
index 89aede0302..ae8b2ed825 100644
--- a/mayan/apps/common/views.py
+++ b/mayan/apps/common/views.py
@@ -28,7 +28,7 @@ from .generics import ( # NOQA
SingleObjectDownloadView, SingleObjectDynamicFormCreateView,
SingleObjectDynamicFormEditView, SingleObjectEditView, SingleObjectListView
)
-from .icons import icon_setup
+from .icons import icon_object_error_list, icon_setup
from .menus import menu_setup, menu_tools
from .permissions_runtime import permission_error_log_view
from .settings import setting_home_view
@@ -211,6 +211,15 @@ class ObjectErrorLogEntryListView(SingleObjectListView):
{'name': _('Result'), 'attribute': 'result'},
),
'hide_object': True,
+ 'no_results_icon': icon_object_error_list,
+ 'no_results_text': _(
+ 'Errors during the operation of some objects are recorded '
+ 'in this view. Some objects automatically clear their '
+ 'errors on subsequent successful operation.'
+ ),
+ 'no_results_title': _(
+ 'There are no errors for this object'
+ ),
'object': self.get_object(),
'title': _('Error log entries for: %s' % self.get_object()),
}
diff --git a/mayan/apps/common/widgets.py b/mayan/apps/common/widgets.py
index 01d4c63af7..3a7e111233 100644
--- a/mayan/apps/common/widgets.py
+++ b/mayan/apps/common/widgets.py
@@ -89,18 +89,17 @@ class TextAreaDiv(forms.widgets.Widget):
class TwoStateWidget(object):
- def __init__(self, state, center=False, icon_ok=None, icon_fail=None):
- self.state = state
+ def __init__(self, center=False, icon_ok=None, icon_fail=None):
self.icon_ok = icon_ok or default_icon_ok
self.icon_fail = icon_fail or default_icon_fail
self.center = center
- def render(self):
+ def render(self, name=None, value=None):
center_class = ''
if self.center:
center_class = 'text-center'
- if self.state:
+ if value:
return mark_safe(
'{}
'.format(
center_class, self.icon_ok.render()
diff --git a/mayan/apps/dashboards/apps.py b/mayan/apps/dashboards/apps.py
index 3e61507952..c84d414242 100644
--- a/mayan/apps/dashboards/apps.py
+++ b/mayan/apps/dashboards/apps.py
@@ -1,6 +1,5 @@
from __future__ import unicode_literals
-from django.apps import apps
from django.utils.translation import ugettext_lazy as _
from mayan.apps.common import MayanAppConfig
diff --git a/mayan/apps/dashboards/classes.py b/mayan/apps/dashboards/classes.py
index 44be741d68..c1e4a6d478 100644
--- a/mayan/apps/dashboards/classes.py
+++ b/mayan/apps/dashboards/classes.py
@@ -1,12 +1,6 @@
from __future__ import unicode_literals
-from django.apps import apps
-from django.db import models
from django.template import loader
-from django.template.response import TemplateResponse
-from django.urls import reverse
-from django.utils.encoding import force_text, python_2_unicode_compatible
-from django.utils.translation import ugettext
class Dashboard(object):
diff --git a/mayan/apps/dashboards/models.py b/mayan/apps/dashboards/models.py
deleted file mode 100644
index 1dfab76043..0000000000
--- a/mayan/apps/dashboards/models.py
+++ /dev/null
@@ -1,6 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import models
-
-# Create your models here.
diff --git a/mayan/apps/dashboards/templatetags/dashboards_tags.py b/mayan/apps/dashboards/templatetags/dashboards_tags.py
index d3cab05e88..bd03c9e816 100644
--- a/mayan/apps/dashboards/templatetags/dashboards_tags.py
+++ b/mayan/apps/dashboards/templatetags/dashboards_tags.py
@@ -1,6 +1,6 @@
from __future__ import unicode_literals
-from django.template import Context, Library
+from django.template import Library
from ..classes import Dashboard
diff --git a/mayan/apps/dashboards/views.py b/mayan/apps/dashboards/views.py
deleted file mode 100644
index e784a0bd2c..0000000000
--- a/mayan/apps/dashboards/views.py
+++ /dev/null
@@ -1,6 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.shortcuts import render
-
-# Create your views here.
diff --git a/mayan/apps/django_gpg/apps.py b/mayan/apps/django_gpg/apps.py
index 0362b64415..2c501de5fd 100644
--- a/mayan/apps/django_gpg/apps.py
+++ b/mayan/apps/django_gpg/apps.py
@@ -44,22 +44,24 @@ class DjangoGPGApp(MayanAppConfig):
)
)
- SourceColumn(source=Key, label=_('Key ID'), attribute='key_id')
- SourceColumn(source=Key, label=_('User ID'), attribute='user_id')
+ SourceColumn(attribute='key_id', is_identifier=True, source=Key)
+ SourceColumn(attribute='user_id', source=Key)
- SourceColumn(source=KeyStub, label=_('Key ID'), attribute='key_id')
- SourceColumn(source=KeyStub, label=_('Type'), attribute='key_type')
SourceColumn(
- source=KeyStub, label=_('Creation date'), attribute='date'
+ attribute='key_id.fget', is_identifier=True, source=KeyStub
+ )
+ SourceColumn(attribute='key_type', label=_('Type'), source=KeyStub)
+ SourceColumn(
+ attribute='date', label=_('Creation date'), source=KeyStub
)
SourceColumn(
- source=KeyStub, label=_('Expiration date'),
- func=lambda context: context['object'].expires or _('No expiration')
+ attribute='expires', empty_value=_('No expiration'),
+ label=_('Expiration date'), source=KeyStub
)
- SourceColumn(source=KeyStub, label=_('Length'), attribute='length')
+ SourceColumn(attribute='length', label=_('Length'), source=KeyStub)
SourceColumn(
- source=KeyStub, label=_('User ID'),
- func=lambda context: ', '.join(context['object'].user_id)
+ func=lambda context: ', '.join(context['object'].user_id),
+ label=_('User ID'), source=KeyStub
)
menu_object.bind_links(links=(link_key_detail,), sources=(Key,))
diff --git a/mayan/apps/django_gpg/classes.py b/mayan/apps/django_gpg/classes.py
index 61c3ebc254..510cc2892d 100644
--- a/mayan/apps/django_gpg/classes.py
+++ b/mayan/apps/django_gpg/classes.py
@@ -6,6 +6,8 @@ import shutil
import gnupg
+from django.utils.translation import ugettext_lazy as _
+
from mayan.apps.common.utils import mkdtemp
@@ -149,6 +151,7 @@ class KeyStub(object):
@property
def key_id(self):
return self.fingerprint[-8:]
+ key_id.fget.short_description = _('Key ID')
class SignatureVerification(object):
diff --git a/mayan/apps/django_gpg/models.py b/mayan/apps/django_gpg/models.py
index 0716f94c91..6ab1aab7f2 100644
--- a/mayan/apps/django_gpg/models.py
+++ b/mayan/apps/django_gpg/models.py
@@ -85,6 +85,7 @@ class Key(models.Model):
Short form key ID (using the first 8 characters).
"""
return self.fingerprint[-8:]
+ key_id.fget.short_description = _('Key ID')
def save(self, *args, **kwargs):
import_results, key_info = gpg_backend.import_and_list_keys(
diff --git a/mayan/apps/document_indexing/apps.py b/mayan/apps/document_indexing/apps.py
index f44b9b95aa..e17e254d3b 100644
--- a/mayan/apps/document_indexing/apps.py
+++ b/mayan/apps/document_indexing/apps.py
@@ -87,9 +87,7 @@ class DocumentIndexingApp(MayanAppConfig):
SourceColumn(attribute='label', is_identifier=True, source=Index)
SourceColumn(attribute='slug', source=Index)
SourceColumn(
- func=lambda context: TwoStateWidget(
- state=context['object'].enabled
- ).render(), label=_('Enabled'), source=Index
+ attribute='enabled', source=Index, widget=TwoStateWidget
)
SourceColumn(
@@ -111,15 +109,12 @@ class DocumentIndexingApp(MayanAppConfig):
label=_('Level'), source=IndexTemplateNode
)
SourceColumn(
- func=lambda context: TwoStateWidget(
- state=context['object'].enabled
- ).render(), label=_('Enabled'), source=IndexTemplateNode
+ attribute='enabled', source=IndexTemplateNode,
+ widget=TwoStateWidget
)
SourceColumn(
- func=lambda context: TwoStateWidget(
- state=context['object'].link_documents
- ).render(), label=_('Has document links?'),
- source=IndexTemplateNode,
+ attribute='link_documents', source=IndexTemplateNode,
+ widget=TwoStateWidget
)
SourceColumn(
diff --git a/mayan/apps/document_signatures/handlers.py b/mayan/apps/document_signatures/handlers.py
index f9bc822608..b493e5ff35 100644
--- a/mayan/apps/document_signatures/handlers.py
+++ b/mayan/apps/document_signatures/handlers.py
@@ -1,9 +1,6 @@
from __future__ import unicode_literals
-from .tasks import (
- task_unverify_key_signatures, task_verify_key_signatures,
- task_verify_missing_embedded_signature
-)
+from .tasks import task_unverify_key_signatures, task_verify_key_signatures
def handler_unverify_key_signatures(sender, **kwargs):
diff --git a/mayan/apps/document_states/apps.py b/mayan/apps/document_states/apps.py
index a4cf5544df..a00df2d808 100644
--- a/mayan/apps/document_states/apps.py
+++ b/mayan/apps/document_states/apps.py
@@ -135,16 +135,14 @@ class DocumentStatesApp(MayanAppConfig):
SourceColumn(
attribute='label', is_identifier=True, source=Workflow
)
- SourceColumn(
- attribute='internal_name', source=Workflow
- )
- SourceColumn(
- attribute='get_initial_state', source=Workflow,
- )
+ SourceColumn(attribute='internal_name', source=Workflow)
+ SourceColumn(attribute='get_initial_state', source=Workflow)
SourceColumn(
- attribute='get_current_state', source=WorkflowInstance
+ attribute='workflow', is_identifier=True,
+ source=WorkflowInstance
)
+ SourceColumn(attribute='get_current_state', source=WorkflowInstance)
SourceColumn(
attribute='get_last_transition_user', source=WorkflowInstance
)
@@ -163,57 +161,40 @@ class DocumentStatesApp(MayanAppConfig):
SourceColumn(
attribute='get_rendered_datetime', source=WorkflowInstanceLogEntry
)
- SourceColumn(
- attribute='user', source=WorkflowInstanceLogEntry
- )
- SourceColumn(
- attribute='transition', source=WorkflowInstanceLogEntry
- )
- SourceColumn(
- attribute='comment', source=WorkflowInstanceLogEntry
- )
+ SourceColumn(attribute='user', source=WorkflowInstanceLogEntry)
+ SourceColumn(attribute='transition', source=WorkflowInstanceLogEntry)
+ SourceColumn(attribute='comment', source=WorkflowInstanceLogEntry)
SourceColumn(
- source=WorkflowState, label=_('Is initial state?'),
- func=lambda context: TwoStateWidget(
- state=context['object'].initial
- ).render()
+ attribute='label', is_identifier=True, source=WorkflowState
)
SourceColumn(
- source=WorkflowState, label=_('Completion'), attribute='completion'
+ attribute='initial', source=WorkflowState, widget=TwoStateWidget
)
+ SourceColumn(attribute='completion', source=WorkflowState)
SourceColumn(
- source=WorkflowStateAction, label=_('Label'), attribute='label'
+ attribute='label', is_identifier=True, source=WorkflowStateAction
)
SourceColumn(
- source=WorkflowStateAction, label=_('Enabled?'),
- func=lambda context: TwoStateWidget(
- state=context['object'].enabled
- ).render()
+ attribute='enabled', source=WorkflowStateAction,
+ widget=TwoStateWidget
)
SourceColumn(
- source=WorkflowStateAction, label=_('When?'),
- attribute='get_when_display'
- )
- SourceColumn(
- source=WorkflowStateAction, label=_('Action type'),
- attribute='get_class_label'
+ attribute='get_when_display', label=_('When?'),
+ source=WorkflowStateAction
)
+ SourceColumn(attribute='get_class_label', source=WorkflowStateAction)
SourceColumn(
- source=WorkflowTransition, label=_('Origin state'),
- attribute='origin_state'
+ attribute='label', is_identifier=True, source=WorkflowTransition
)
+ SourceColumn(attribute='origin_state', source=WorkflowTransition)
+ SourceColumn(attribute='destination_state', source=WorkflowTransition)
SourceColumn(
- source=WorkflowTransition, label=_('Destination state'),
- attribute='destination_state'
- )
- SourceColumn(
- source=WorkflowTransition, label=_('Triggers'),
func=lambda context: widget_transition_events(
transition=context['object']
- )
+ ), label=_('Triggers'), source=WorkflowTransition
)
app.conf.task_queues.extend(
diff --git a/mayan/apps/document_states/models.py b/mayan/apps/document_states/models.py
index 293f919398..c3f8f1a92b 100644
--- a/mayan/apps/document_states/models.py
+++ b/mayan/apps/document_states/models.py
@@ -318,6 +318,7 @@ class WorkflowStateAction(models.Model):
def get_class_label(self):
return self.get_class().label
+ get_class_label.short_description = _('Action type')
def loads(self):
return json.loads(self.action_data)
diff --git a/mayan/apps/document_states/views.py b/mayan/apps/document_states/views.py
index 457d404880..a7f789109d 100644
--- a/mayan/apps/document_states/views.py
+++ b/mayan/apps/document_states/views.py
@@ -62,7 +62,7 @@ class DocumentWorkflowInstanceListView(SingleObjectListView):
def get_extra_context(self):
return {
- 'hide_link': True,
+ 'hide_object': True,
'no_results_icon': icon_workflow_list,
'no_results_text': _(
'Assign workflows to the document type of this document '
@@ -96,6 +96,13 @@ class WorkflowInstanceDetailView(SingleObjectListView):
return {
'hide_object': True,
'navigation_object_list': ('object', 'workflow_instance'),
+ 'no_results_text': _(
+ 'This view will show the states changed as a workflow '
+ 'instance is transitioned.'
+ ),
+ 'no_results_title': _(
+ 'There are no details for this workflow instance'
+ ),
'object': self.get_workflow_instance().document,
'title': _('Detail of workflow: %(workflow)s') % {
'workflow': self.get_workflow_instance()
@@ -509,7 +516,7 @@ class SetupWorkflowStateListView(SingleObjectListView):
def get_extra_context(self):
return {
- 'hide_link': True,
+ 'hide_object': True,
'no_results_icon': icon_workflow_state,
'no_results_main_link': link_setup_workflow_state_create.resolve(
context=RequestContext(
@@ -624,7 +631,7 @@ class SetupWorkflowTransitionListView(SingleObjectListView):
def get_extra_context(self):
return {
- 'hide_link': True,
+ 'hide_object': True,
'no_results_icon': icon_workflow_transition,
'no_results_main_link': link_setup_workflow_transition_create.resolve(
context=RequestContext(
diff --git a/mayan/apps/documents/apps.py b/mayan/apps/documents/apps.py
index 06a0a9c4c2..5686d8d0f3 100644
--- a/mayan/apps/documents/apps.py
+++ b/mayan/apps/documents/apps.py
@@ -104,10 +104,7 @@ from .queues import * # NOQA
from .search import document_page_search, document_search # NOQA
from .signals import post_version_upload
from .statistics import * # NOQA
-from .widgets import (
- DocumentPageThumbnailWidget, widget_document_page_number,
- widget_document_version_page_number
-)
+from .widgets import DocumentPageThumbnailWidget
class DocumentsApp(MayanAppConfig):
@@ -129,7 +126,7 @@ class DocumentsApp(MayanAppConfig):
DocumentType = self.get_model('DocumentType')
DocumentTypeFilename = self.get_model('DocumentTypeFilename')
DocumentVersion = self.get_model('DocumentVersion')
- DuplicatedDocument = self.get_model('DuplicatedDocument')
+ DuplicatedDocumentProxy = self.get_model('DuplicatedDocumentProxy')
DynamicSerializerField.add_serializer(
klass=Document,
@@ -244,9 +241,7 @@ class DocumentsApp(MayanAppConfig):
SourceColumn(
attribute='document_type', label=_('Type'), source=Document
)
- SourceColumn(
- func=widget_document_page_number, label=_('Pages'), source=Document
- )
+ SourceColumn(attribute='get_page_count', source=Document)
# DocumentPage
SourceColumn(
@@ -274,9 +269,8 @@ class DocumentsApp(MayanAppConfig):
)
SourceColumn(
- func=lambda context: TwoStateWidget(
- state=context['object'].enabled
- ).render(), label=_('Enabled'), source=DocumentTypeFilename
+ attribute='enabled', source=DocumentTypeFilename,
+ widget=TwoStateWidget
)
# DeletedDocument
@@ -290,41 +284,34 @@ class DocumentsApp(MayanAppConfig):
attribute='document_type', source=DeletedDocument
)
SourceColumn(
- attribute='deleted_date_time', source=DeletedDocument
+ attribute='get_rendered_deleted_date_time', source=DeletedDocument
)
# DocumentVersion
+ SourceColumn(
+ attribute='get_rendered_timestamp', is_identifier=True,
+ source=DocumentVersion
+ )
SourceColumn(
func=lambda context: document_page_thumbnail_widget.render(
instance=context['object']
), label=_('Thumbnail'), source=DocumentVersion
)
- SourceColumn(
- attribute='timestamp', source=DocumentVersion
- )
- SourceColumn(
- func=widget_document_version_page_number, label=_('Pages'),
- source=DocumentVersion
- )
- SourceColumn(
- attribute='mimetype', source=DocumentVersion
- )
- SourceColumn(
- attribute='encoding', source=DocumentVersion
- )
- SourceColumn(
- attribute='comment', source=DocumentVersion
- )
+ SourceColumn(attribute='get_page_count', source=DocumentVersion)
+ SourceColumn(attribute='mimetype', source=DocumentVersion)
+ SourceColumn(attribute='encoding', source=DocumentVersion)
+ SourceColumn(attribute='comment', source=DocumentVersion)
# DuplicatedDocument
SourceColumn(
func=lambda context: document_page_thumbnail_widget.render(
- instance=context['object'].document
- ), label=_('Thumbnail'), source=DuplicatedDocument
+ instance=context['object']
+ ), label=_('Thumbnail'), source=DuplicatedDocumentProxy
)
SourceColumn(
- func=lambda context: context['object'].documents.count(),
- label=_('Duplicates'), source=DuplicatedDocument
+ func=lambda context: context['object'].get_duplicate_count(
+ user=context['request'].user,
+ ), label=_('Duplicates'), source=DuplicatedDocumentProxy
)
app.conf.beat_schedule.update(
diff --git a/mayan/apps/documents/managers.py b/mayan/apps/documents/managers.py
index 974291c21f..bdc1ec343d 100644
--- a/mayan/apps/documents/managers.py
+++ b/mayan/apps/documents/managers.py
@@ -128,10 +128,10 @@ class DuplicatedDocumentManager(models.Manager):
self.filter(documents=None).delete()
def get_duplicated_documents(self):
- Document = apps.get_model(
- app_label='documents', model_name='Document'
+ DuplicatedDocumentProxy = apps.get_model(
+ app_label='documents', model_name='DuplicatedDocumentProxy'
)
- return Document.objects.filter(
+ return DuplicatedDocumentProxy.objects.filter(
pk__in=self.filter(documents__isnull=False).values_list(
'document_id', flat=True
)
diff --git a/mayan/apps/documents/migrations/0050_duplicateddocumentproxy.py b/mayan/apps/documents/migrations/0050_duplicateddocumentproxy.py
new file mode 100644
index 0000000000..df43fbd240
--- /dev/null
+++ b/mayan/apps/documents/migrations/0050_duplicateddocumentproxy.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.16 on 2018-12-22 09:30
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('documents', '0049_auto_20181211_0011'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='DuplicatedDocumentProxy',
+ fields=[
+ ],
+ options={
+ 'verbose_name': 'Duplicated document',
+ 'proxy': True,
+ 'verbose_name_plural': 'Duplicated documents',
+ 'indexes': [],
+ },
+ bases=('documents.document',),
+ ),
+ ]
diff --git a/mayan/apps/documents/models/document_models.py b/mayan/apps/documents/models/document_models.py
index 33fdf31a86..142a9e9159 100644
--- a/mayan/apps/documents/models/document_models.py
+++ b/mayan/apps/documents/models/document_models.py
@@ -7,12 +7,15 @@ from django.apps import apps
from django.conf import settings
from django.core.files import File
from django.db import models
+from django.template import Context, Template
from django.urls import reverse
from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.timezone import now
from django.utils.translation import ugettext
from django.utils.translation import ugettext_lazy as _
+from mayan.apps.acls.models import AccessControlList
+
from ..events import (
event_document_create, event_document_properties_edit,
event_document_type_change,
@@ -21,6 +24,7 @@ from ..managers import (
DocumentManager, DuplicatedDocumentManager, FavoriteDocumentManager,
PassthroughManager, RecentDocumentManager, TrashCanManager
)
+from ..permissions import permission_document_view
from ..settings import setting_language
from ..signals import post_document_type_change
@@ -77,7 +81,6 @@ class Document(models.Model):
'Whether or not this document is in the trash.'
), editable=False, verbose_name=_('In trash?')
)
- # TODO: set editable to False
deleted_date_time = models.DateTimeField(
blank=True, editable=True, help_text=_(
'The server date and time when the document was moved to the '
@@ -139,6 +142,24 @@ class Document(models.Model):
if latest_version:
return latest_version.get_api_image_url(*args, **kwargs)
+ def get_duplicates(self):
+ try:
+ return DuplicatedDocument.objects.get(document=self).documents.all()
+ except DuplicatedDocument.DoesNotExist:
+ return Document.objects.none()
+
+ def get_page_count(self):
+ return self.pages.count()
+ get_page_count.short_description = _('Pages')
+
+ def get_rendered_deleted_date_time(self):
+ return Template('{{ instance.deleted_date_time }}').render(
+ context=Context({'instance': self})
+ )
+ get_rendered_deleted_date_time.short_description = _(
+ 'Date and time trashed'
+ )
+
def invalidate_cache(self):
for document_version in self.versions.all():
document_version.invalidate_cache()
@@ -285,6 +306,20 @@ class DuplicatedDocument(models.Model):
return force_text(self.document)
+class DuplicatedDocumentProxy(Document):
+ class Meta:
+ proxy = True
+ verbose_name = _('Duplicated document')
+ verbose_name_plural = _('Duplicated documents')
+
+ def get_duplicate_count(self, user):
+ queryset = AccessControlList.objects.filter_by_access(
+ permission=permission_document_view, user=user,
+ queryset=self.get_duplicates()
+ )
+ return queryset.count()
+
+
@python_2_unicode_compatible
class FavoriteDocument(models.Model):
"""
diff --git a/mayan/apps/documents/models/document_type_models.py b/mayan/apps/documents/models/document_type_models.py
index 1b971b7da9..0617c6abcc 100644
--- a/mayan/apps/documents/models/document_type_models.py
+++ b/mayan/apps/documents/models/document_type_models.py
@@ -87,10 +87,11 @@ class DocumentType(models.Model):
def get_document_count(self, user):
queryset = AccessControlList.objects.filter_by_access(
- permission_document_view, user, queryset=self.documents
+ permission=permission_document_view, user=user,
+ queryset=self.documents
)
-
return queryset.count()
+ get_document_count.short_description = _('Documents')
def natural_key(self):
return (self.label,)
diff --git a/mayan/apps/documents/models/document_version_models.py b/mayan/apps/documents/models/document_version_models.py
index 148155b77f..81dd48d740 100644
--- a/mayan/apps/documents/models/document_version_models.py
+++ b/mayan/apps/documents/models/document_version_models.py
@@ -181,6 +181,10 @@ class DocumentVersion(models.Model):
)
raise
+ def get_page_count(self):
+ return self.pages.count()
+ get_page_count.short_description = _('Pages')
+
def get_rendered_string(self, preserve_extension=False):
if preserve_extension:
filename, extension = os.path.splitext(self.document.label)
@@ -196,6 +200,7 @@ class DocumentVersion(models.Model):
return Template('{{ instance.timestamp }}').render(
context=Context({'instance': self})
)
+ get_rendered_timestamp.short_description = _('Date and time')
def natural_key(self):
return (self.checksum, self.document.natural_key())
diff --git a/mayan/apps/documents/views/document_views.py b/mayan/apps/documents/views/document_views.py
index db9cc00b29..511416fa57 100644
--- a/mayan/apps/documents/views/document_views.py
+++ b/mayan/apps/documents/views/document_views.py
@@ -20,7 +20,6 @@ from mayan.apps.common.generics import (
SingleObjectDownloadView, SingleObjectEditView, SingleObjectListView
)
from mayan.apps.common.mixins import MultipleInstanceActionMixin
-from mayan.apps.common.utils import encapsulate
from mayan.apps.converter.models import Transformation
from mayan.apps.converter.permissions import (
permission_transformation_delete, permission_transformation_edit
@@ -259,12 +258,7 @@ class DocumentDuplicatesListView(DocumentListView):
return context
def get_object_list(self):
- try:
- return DuplicatedDocument.objects.get(
- document=self.get_document()
- ).documents.all()
- except DuplicatedDocument.DoesNotExist:
- return Document.objects.none()
+ return self.get_document().get_duplicates()
class DocumentEditView(SingleObjectEditView):
@@ -842,16 +836,6 @@ class DuplicatedDocumentListView(DocumentListView):
context = super(DuplicatedDocumentListView, self).get_extra_context()
context.update(
{
- 'extra_columns': (
- {
- 'name': _('Duplicates'),
- 'attribute': encapsulate(
- lambda document: DuplicatedDocument.objects.get(
- document=document
- ).documents.count()
- )
- },
- ),
'no_results_icon': icon_duplicated_document_list,
'no_results_text': _(
'Duplicates are documents that are composed of the exact '
diff --git a/mayan/apps/documents/widgets.py b/mayan/apps/documents/widgets.py
index 1335b61c9e..7496043e6d 100644
--- a/mayan/apps/documents/widgets.py
+++ b/mayan/apps/documents/widgets.py
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
from django import forms
from django.template.loader import render_to_string
from django.utils.safestring import mark_safe
-from django.utils.translation import ugettext_lazy as _
from .settings import (
setting_display_height, setting_display_width, setting_preview_height,
@@ -75,11 +74,3 @@ def document_link(document):
return mark_safe('%s' % (
document.get_absolute_url(), document)
)
-
-
-def widget_document_page_number(context):
- return context['object'].pages.count()
-
-
-def widget_document_version_page_number(context):
- return context['object'].pages.count()
diff --git a/mayan/apps/events/apps.py b/mayan/apps/events/apps.py
index 76ec0d3a2e..0604a32413 100644
--- a/mayan/apps/events/apps.py
+++ b/mayan/apps/events/apps.py
@@ -18,7 +18,9 @@ from .links import (
link_notification_mark_read_all, link_user_events,
link_user_notifications_list
)
-from .widgets import event_object_link, event_type_link, event_user_link
+from .widgets import (
+ widget_event_object_link, widget_event_type_link, widget_event_user_link
+)
class EventsApp(MayanAppConfig):
@@ -37,21 +39,22 @@ class EventsApp(MayanAppConfig):
User = get_user_model()
SourceColumn(
- source=Action, label=_('Timestamp'), attribute='timestamp'
+ attribute='timestamp', is_identifier=True,
+ label=_('Date and time'), source=Action
)
SourceColumn(
- source=Action, label=_('Actor'),
- func=lambda context: event_user_link(context['object'])
+ func=widget_event_user_link, label=_('Actor'), source=Action
)
SourceColumn(
- source=Action, label=_('Event'),
- func=lambda context: event_type_link(context['object'])
+ func=widget_event_type_link, label=_('Event'), source=Action
)
SourceColumn(
- source=Action, label=_('Action object'),
- func=lambda context: event_object_link(
- entry=context['object'], attribute='action_object'
- )
+ func=widget_event_object_link, kwargs={
+ 'attribute': 'action_object'
+ }, label=_('Action object'), source=Action
+ )
+ SourceColumn(
+ func=widget_event_object_link, label=_('Target'), source=Action
)
SourceColumn(
@@ -62,25 +65,27 @@ class EventsApp(MayanAppConfig):
)
SourceColumn(
- source=Notification, label=_('Timestamp'),
- attribute='action.timestamp'
+ attribute='action.timestamp', is_identifier=True,
+ label=_('Date and time'), source=Notification
+
)
SourceColumn(
- source=Notification, label=_('Actor'), attribute='action.actor'
+ func=widget_event_user_link, kwargs={'attribute': 'action'},
+ label=_('Actor'), source=Notification
)
SourceColumn(
- source=Notification, label=_('Event'),
- func=lambda context: event_type_link(context['object'].action)
+ func=widget_event_type_link, kwargs={'attribute': 'action'},
+ label=_('Event'), source=Notification
+ )
+
+ SourceColumn(
+ func=widget_event_object_link, kwargs={
+ 'attribute': 'action.target'
+ }, label=_('Target'), source=Notification
)
SourceColumn(
- source=Notification, label=_('Target'),
- func=lambda context: event_object_link(context['object'].action)
- )
- SourceColumn(
- source=Notification, label=_('Seen'),
- func=lambda context: TwoStateWidget(
- state=context['object'].read
- ).render()
+ attribute='read', label=_('Seen'), source=Notification,
+ widget=TwoStateWidget
)
menu_list_facet.bind_links(
diff --git a/mayan/apps/events/links.py b/mayan/apps/events/links.py
index 7fc06e4ee4..c8b5c7365c 100644
--- a/mayan/apps/events/links.py
+++ b/mayan/apps/events/links.py
@@ -32,10 +32,6 @@ def get_kwargs_factory(variable_name):
return get_kwargs
-def get_notification_count(context):
- return context['request'].user.notifications.filter(read=False).count()
-
-
link_current_user_events = Link(
icon_class=icon_events_user_list, text=_('My events'),
view='events:current_user_events'
diff --git a/mayan/apps/events/views.py b/mayan/apps/events/views.py
index b25a0ebff5..3ddc5df8b8 100644
--- a/mayan/apps/events/views.py
+++ b/mayan/apps/events/views.py
@@ -13,7 +13,6 @@ from actstream.models import Action, any_stream
from mayan.apps.acls.models import AccessControlList
from mayan.apps.common.generics import FormView, SimpleView
-from mayan.apps.common.utils import encapsulate
from mayan.apps.common.views import SingleObjectListView
from .classes import EventType, ModelEventType
@@ -26,7 +25,6 @@ from .icons import (
from .links import link_event_types_subscriptions_list
from .models import StoredEventType
from .permissions import permission_events_view
-from .widgets import event_object_link
class EventListView(SingleObjectListView):
@@ -34,14 +32,6 @@ class EventListView(SingleObjectListView):
def get_extra_context(self):
return {
- 'extra_columns': (
- {
- 'name': _('Target'),
- 'attribute': encapsulate(
- lambda entry: event_object_link(entry)
- )
- },
- ),
'hide_object': True,
'title': _('Events'),
}
@@ -270,14 +260,6 @@ class UserEventListView(SingleObjectListView):
def get_extra_context(self):
return {
- 'extra_columns': (
- {
- 'name': _('Target'),
- 'attribute': encapsulate(
- lambda entry: event_object_link(entry)
- )
- },
- ),
'hide_object': True,
'no_results_icon': icon_events_user_list,
'no_results_text': _(
@@ -308,14 +290,6 @@ class CurrentUserEventListView(UserEventListView):
class VerbEventListView(SingleObjectListView):
def get_extra_context(self):
return {
- 'extra_columns': (
- {
- 'name': _('Target'),
- 'attribute': encapsulate(
- lambda entry: event_object_link(entry)
- )
- },
- ),
'hide_object': True,
'title': _(
'Events of type: %s'
diff --git a/mayan/apps/events/widgets.py b/mayan/apps/events/widgets.py
index 0c69c9c6cd..0563992c22 100644
--- a/mayan/apps/events/widgets.py
+++ b/mayan/apps/events/widgets.py
@@ -5,15 +5,18 @@ from django.utils.encoding import force_text
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
+from mayan.apps.common.utils import resolve_attribute
+
from .classes import EventType
-def event_object_link(entry, attribute='target'):
+def widget_event_object_link(context, attribute='target'):
+ entry = context['object']
label = ''
url = '#'
obj_type = ''
- obj = getattr(entry, attribute)
+ obj = resolve_attribute(obj=entry, attribute=attribute)
if obj:
obj_type = '{}: '.format(obj._meta.verbose_name)
@@ -28,7 +31,12 @@ def event_object_link(entry, attribute='target'):
)
-def event_type_link(entry):
+def widget_event_type_link(context, attribute=None):
+ entry = context['object']
+
+ if attribute:
+ entry = getattr(entry, attribute)
+
return mark_safe(
'%(label)s' % {
'url': reverse('events:events_by_verb', kwargs={'verb': entry.verb}),
@@ -37,7 +45,12 @@ def event_type_link(entry):
)
-def event_user_link(entry):
+def widget_event_user_link(context, attribute=None):
+ entry = context['object']
+
+ if attribute:
+ entry = getattr(entry, attribute)
+
if entry.actor == entry.target:
return _('System')
else:
diff --git a/mayan/apps/file_metadata/apps.py b/mayan/apps/file_metadata/apps.py
index 29ef506768..7310d6b3e6 100644
--- a/mayan/apps/file_metadata/apps.py
+++ b/mayan/apps/file_metadata/apps.py
@@ -121,16 +121,17 @@ class FileMetadataApp(MayanAppConfig):
model=DocumentTypeSettings, related='document_type',
)
- SourceColumn(source=FileMetadataEntry, attribute='key')
- SourceColumn(source=FileMetadataEntry, attribute='value')
+ SourceColumn(attribute='key', source=FileMetadataEntry)
+ SourceColumn(attribute='value', source=FileMetadataEntry)
SourceColumn(
- source=DocumentVersionDriverEntry, attribute='driver'
+ attribute='driver', source=DocumentVersionDriverEntry
)
SourceColumn(
- source=DocumentVersionDriverEntry, attribute='driver__internal_name'
+ attribute='driver__internal_name',
+ source=DocumentVersionDriverEntry
)
SourceColumn(
- source=DocumentVersionDriverEntry, attribute='get_attribute_count'
+ attribute='get_attribute_count', source=DocumentVersionDriverEntry
)
app.conf.task_queues.append(
diff --git a/mayan/apps/file_metadata/migrations/0002_auto_20181222_0931.py b/mayan/apps/file_metadata/migrations/0002_auto_20181222_0931.py
new file mode 100644
index 0000000000..0b267ea4c8
--- /dev/null
+++ b/mayan/apps/file_metadata/migrations/0002_auto_20181222_0931.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.16 on 2018-12-22 09:31
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('file_metadata', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='filemetadataentry',
+ name='key',
+ field=models.CharField(db_index=True, help_text='Name of the file metadata entry.', max_length=255, verbose_name='Key'),
+ ),
+ migrations.AlterField(
+ model_name='filemetadataentry',
+ name='value',
+ field=models.CharField(db_index=True, help_text='Value of the file metadata entry.', max_length=255, verbose_name='Value'),
+ ),
+ ]
diff --git a/mayan/apps/linking/apps.py b/mayan/apps/linking/apps.py
index 2d8f8b319a..d22d6de8c8 100644
--- a/mayan/apps/linking/apps.py
+++ b/mayan/apps/linking/apps.py
@@ -57,28 +57,22 @@ class LinkingApp(MayanAppConfig):
)
SourceColumn(
- source=ResolvedSmartLink, label=_('Label'),
func=lambda context: context['object'].get_label_for(
document=context['document']
- )
+ ), label=_('Label'), source=ResolvedSmartLink
)
SourceColumn(
- source=SmartLink, label=_('Dynamic label'),
- attribute='dynamic_label'
+ attribute='label', is_identifier=True, source=SmartLink
)
+ SourceColumn(attribute='dynamic_label', source=SmartLink)
SourceColumn(
- source=SmartLink, label=_('Enabled'),
- func=lambda context: TwoStateWidget(
- state=context['object'].enabled
- ).render()
+ attribute='enabled', source=SmartLink, widget=TwoStateWidget
)
SourceColumn(
- source=SmartLinkCondition, label=_('Enabled'),
- func=lambda context: TwoStateWidget(
- state=context['object'].enabled
- ).render()
+ attribute='enabled', source=SmartLinkCondition,
+ widget=TwoStateWidget
)
menu_facet.bind_links(
diff --git a/mayan/apps/linking/views.py b/mayan/apps/linking/views.py
index 2a71f89d64..ae6fd359de 100644
--- a/mayan/apps/linking/views.py
+++ b/mayan/apps/linking/views.py
@@ -139,7 +139,7 @@ class SmartLinkListView(SingleObjectListView):
def get_extra_context(self):
return {
- 'hide_link': True,
+ 'hide_object': True,
'no_results_icon': icon_smart_link_setup,
'no_results_main_link': link_smart_link_create.resolve(
context=RequestContext(request=self.request)
diff --git a/mayan/apps/mailer/apps.py b/mayan/apps/mailer/apps.py
index aaad974d2a..20e63974a3 100644
--- a/mayan/apps/mailer/apps.py
+++ b/mayan/apps/mailer/apps.py
@@ -53,30 +53,16 @@ class MailerApp(MayanAppConfig):
MailerBackend.initialize()
+ SourceColumn(attribute='datetime', source=LogEntry)
+ SourceColumn(attribute='message', source=LogEntry)
+ SourceColumn(attribute='label', is_identifier=True, source=UserMailer)
SourceColumn(
- source=LogEntry, label=_('Date and time'), attribute='datetime'
+ attribute='default', source=UserMailer, widget=TwoStateWidget
)
SourceColumn(
- source=LogEntry, label=_('Message'), attribute='message'
- )
- SourceColumn(
- source=UserMailer, label=_('Label'), attribute='label'
- )
- SourceColumn(
- source=UserMailer, label=_('Default?'),
- func=lambda context: TwoStateWidget(
- state=context['object'].default
- ).render()
- )
- SourceColumn(
- source=UserMailer, label=_('Enabled?'),
- func=lambda context: TwoStateWidget(
- state=context['object'].enabled
- ).render()
- )
- SourceColumn(
- source=UserMailer, label=_('Label'), attribute='backend_label'
+ attribute='enabled', source=UserMailer, widget=TwoStateWidget
)
+ SourceColumn(attribute='backend_label', source=UserMailer)
ModelPermission.register(
model=Document, permissions=(
diff --git a/mayan/apps/mailer/models.py b/mayan/apps/mailer/models.py
index 064b1e15b7..f4fd5449a4 100644
--- a/mayan/apps/mailer/models.py
+++ b/mayan/apps/mailer/models.py
@@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
class LogEntry(models.Model):
datetime = models.DateTimeField(
- auto_now_add=True, editable=False, verbose_name=_('Date time')
+ auto_now_add=True, editable=False, verbose_name=_('Date and time')
)
message = models.TextField(
blank=True, editable=False, verbose_name=_('Message')
@@ -76,6 +76,7 @@ class UserMailer(models.Model):
property.
"""
return self.get_backend().label
+ backend_label.short_description = _('Backend label')
def dumps(self, data):
"""
diff --git a/mayan/apps/metadata/api.py b/mayan/apps/metadata/api.py
index a1805c7f27..2fee53ae1e 100644
--- a/mayan/apps/metadata/api.py
+++ b/mayan/apps/metadata/api.py
@@ -35,15 +35,41 @@ def decode_metadata_from_querystring(querystring=None):
return metadata_list
-def save_metadata_list(metadata_list, document, create=False, _user=None):
+def metadata_repr(metadata_list):
"""
- Take a list of metadata dictionaries and associate them to a
- document
+ Return a printable representation of a metadata list
"""
- for item in metadata_list:
- save_metadata(
- metadata_dict=item, document=document, create=create, _user=_user
- )
+ return ', '.join(metadata_repr_as_list(metadata_list))
+
+
+def metadata_repr_as_list(metadata_list):
+ """
+ Turn a list of metadata into a list of printable representations
+ """
+ output = []
+ for metadata_dict in metadata_list:
+ try:
+ output.append('%s - %s' % (MetadataType.objects.get(
+ pk=metadata_dict['id']), metadata_dict.get('value', '')))
+ except Exception:
+ pass
+
+ return output
+
+
+def set_bulk_metadata(document, metadata_dictionary):
+ document_type = document.document_type
+ document_type_metadata_types = [
+ document_type_metadata_type.metadata_type for document_type_metadata_type in document_type.metadata.all()
+ ]
+
+ for metadata_type_name, value in metadata_dictionary.items():
+ metadata_type = MetadataType.objects.get(name=metadata_type_name)
+
+ if metadata_type in document_type_metadata_types:
+ DocumentMetadata.objects.get_or_create(
+ document=document, metadata_type=metadata_type, value=value
+ )
def save_metadata(metadata_dict, document, create=False, _user=None):
@@ -84,38 +110,12 @@ def save_metadata(metadata_dict, document, create=False, _user=None):
document_metadata.save(_user=_user)
-def metadata_repr(metadata_list):
+def save_metadata_list(metadata_list, document, create=False, _user=None):
"""
- Return a printable representation of a metadata list
+ Take a list of metadata dictionaries and associate them to a
+ document
"""
- return ', '.join(metadata_repr_as_list(metadata_list))
-
-
-def metadata_repr_as_list(metadata_list):
- """
- Turn a list of metadata into a list of printable representations
- """
- output = []
- for metadata_dict in metadata_list:
- try:
- output.append('%s - %s' % (MetadataType.objects.get(
- pk=metadata_dict['id']), metadata_dict.get('value', '')))
- except Exception:
- pass
-
- return output
-
-
-def set_bulk_metadata(document, metadata_dictionary):
- document_type = document.document_type
- document_type_metadata_types = [
- document_type_metadata_type.metadata_type for document_type_metadata_type in document_type.metadata.all()
- ]
-
- for metadata_type_name, value in metadata_dictionary.items():
- metadata_type = MetadataType.objects.get(name=metadata_type_name)
-
- if metadata_type in document_type_metadata_types:
- DocumentMetadata.objects.get_or_create(
- document=document, metadata_type=metadata_type, value=value
- )
+ for item in metadata_list:
+ save_metadata(
+ metadata_dict=item, document=document, create=create, _user=_user
+ )
diff --git a/mayan/apps/metadata/apps.py b/mayan/apps/metadata/apps.py
index b8929c1cef..c6b60776ee 100644
--- a/mayan/apps/metadata/apps.py
+++ b/mayan/apps/metadata/apps.py
@@ -54,7 +54,7 @@ from .permissions import (
)
from .queues import * # NOQA
from .search import metadata_type_search # NOQA
-from .widgets import get_metadata_string
+from .widgets import widget_get_metadata_string
logger = logging.getLogger(__name__)
@@ -144,27 +144,27 @@ class MetadataApp(MayanAppConfig):
)
SourceColumn(
- source=Document, label=_('Metadata'),
- func=lambda context: get_metadata_string(context['object'])
+ func=widget_get_metadata_string, source=Document
)
SourceColumn(
- source=DocumentPageSearchResult, label=_('Metadata'),
- func=lambda context: get_metadata_string(
- context['object'].document
- )
+ func=widget_get_metadata_string, kwargs={'attribute': 'document'},
+ source=DocumentPageSearchResult,
)
SourceColumn(
- source=DocumentMetadata, label=_('Value'),
- attribute='value'
+ attribute='metadata_type', is_identifier=True,
+ source=DocumentMetadata
+ )
+ SourceColumn(attribute='value', source=DocumentMetadata)
+ SourceColumn(
+ attribute='is_required', source=DocumentMetadata,
+ widget=TwoStateWidget
)
SourceColumn(
- source=DocumentMetadata, label=_('Required'),
- func=lambda context: TwoStateWidget(
- state=context['object'].is_required
- ).render()
+ attribute='label', is_identifier=True, source=MetadataType
)
+ SourceColumn(attribute='name', source=MetadataType)
app.conf.task_queues.append(
Queue('metadata', Exchange('metadata'), routing_key='metadata'),
diff --git a/mayan/apps/metadata/models.py b/mayan/apps/metadata/models.py
index 8f55d39ac7..20e4a767fb 100644
--- a/mayan/apps/metadata/models.py
+++ b/mayan/apps/metadata/models.py
@@ -265,6 +265,7 @@ class DocumentMetadata(models.Model):
return self.metadata_type.get_required_for(
document_type=self.document.document_type
)
+ is_required.fget.short_description = _('Required')
def save(self, *args, **kwargs):
if self.metadata_type.pk not in self.document.document_type.metadata.values_list('metadata_type', flat=True):
diff --git a/mayan/apps/metadata/views.py b/mayan/apps/metadata/views.py
index e74db2a23b..4c5879b156 100644
--- a/mayan/apps/metadata/views.py
+++ b/mayan/apps/metadata/views.py
@@ -402,7 +402,7 @@ class DocumentMetadataListView(SingleObjectListView):
def get_extra_context(self):
document = self.get_document()
return {
- 'hide_link': True,
+ 'hide_object': True,
'object': document,
'no_results_icon': icon_metadata,
'no_results_main_link': link_metadata_add.resolve(
@@ -618,13 +618,7 @@ class MetadataTypeListView(SingleObjectListView):
def get_extra_context(self):
return {
- 'extra_columns': (
- {
- 'name': _('Internal name'),
- 'attribute': 'name',
- },
- ),
- 'hide_link': True,
+ 'hide_object': True,
'no_results_icon': icon_metadata,
'no_results_main_link': link_setup_metadata_type_create.resolve(
context=RequestContext(request=self.request)
diff --git a/mayan/apps/metadata/widgets.py b/mayan/apps/metadata/widgets.py
index 8e4a428069..2342469905 100644
--- a/mayan/apps/metadata/widgets.py
+++ b/mayan/apps/metadata/widgets.py
@@ -1,17 +1,26 @@
from __future__ import unicode_literals
from django.utils.html import format_html_join
+from django.utils.translation import ugettext_lazy as _
-def get_metadata_string(document):
+def widget_get_metadata_string(context, attribute=None):
"""
Return a formated representation of a document's metadata values
"""
+ obj = context['object']
+
+ if attribute:
+ obj = getattr(context['object'], attribute)
+
return format_html_join(
'\n', '{}: {}
',
(
(
document_metadata.metadata_type, document_metadata.metadata_type_id, document_metadata.id, document_metadata.value
- ) for document_metadata in document.metadata.all()
+ ) for document_metadata in obj.metadata.all()
)
)
+
+
+widget_get_metadata_string.short_description = _('Metadata')
diff --git a/mayan/apps/motd/apps.py b/mayan/apps/motd/apps.py
index cbdb7d5271..fdc4b4f8b1 100644
--- a/mayan/apps/motd/apps.py
+++ b/mayan/apps/motd/apps.py
@@ -12,6 +12,7 @@ from mayan.apps.acls.permissions import (
from mayan.apps.common import (
MayanAppConfig, menu_list_facet, menu_object, menu_secondary, menu_setup
)
+from mayan.apps.common.widgets import TwoStateWidget
from mayan.apps.navigation import SourceColumn
from .links import (
@@ -45,16 +46,18 @@ class MOTDApp(MayanAppConfig):
permission_message_view
)
)
+
SourceColumn(
- source=Message, label=_('Enabled'), attribute='enabled'
+ attribute='label', is_identifier=True, source=Message
)
SourceColumn(
- source=Message, label=_('Start date time'),
- func=lambda context: context['object'].start_datetime or _('None')
+ attribute='enabled', source=Message, widget=TwoStateWidget
)
SourceColumn(
- source=Message, label=_('End date time'),
- func=lambda context: context['object'].end_datetime or _('None')
+ attribute='start_datetime', empty_value=_('None'), source=Message
+ )
+ SourceColumn(
+ attribute='end_datetime', empty_value=_('None'), source=Message
)
menu_list_facet.bind_links(
diff --git a/mayan/apps/motd/views.py b/mayan/apps/motd/views.py
index 7899db6bf8..d16475647f 100644
--- a/mayan/apps/motd/views.py
+++ b/mayan/apps/motd/views.py
@@ -65,7 +65,7 @@ class MessageListView(SingleObjectListView):
def get_extra_context(self):
return {
- 'hide_link': True,
+ 'hide_object': True,
'no_results_icon': icon_message_list,
'no_results_main_link': link_message_create.resolve(
context=RequestContext(request=self.request)
diff --git a/mayan/apps/navigation/classes.py b/mayan/apps/navigation/classes.py
index c865105937..6dd5cc8e72 100644
--- a/mayan/apps/navigation/classes.py
+++ b/mayan/apps/navigation/classes.py
@@ -15,10 +15,13 @@ from django.template import VariableDoesNotExist, Variable
from django.template.defaulttags import URLNode
from django.urls import Resolver404, resolve
from django.utils.encoding import force_str, force_text
+from django.utils.translation import ugettext_lazy as _
from mayan.apps.common.utils import resolve_attribute
from mayan.apps.permissions import Permission
+from .exceptions import NavigationError
+
logger = logging.getLogger(__name__)
@@ -527,10 +530,11 @@ class SourceColumn(object):
else:
return result
- def __init__(self, source, label=None, attribute=None, func=None, kwargs=None, order=None, is_identifier=False):
+ def __init__(self, source, attribute=None, empty_value=None, func=None, is_identifier=False, kwargs=None, label=None, order=None, widget=None):
self.source = source
self._label = label
self.attribute = attribute
+ self.empty_value = empty_value
self.func = func
self.kwargs = kwargs or {}
self.order = order or 0
@@ -538,20 +542,29 @@ class SourceColumn(object):
self.__class__._registry.setdefault(source, [])
self.__class__._registry[source].append(self)
self.label = None
+ self.widget = widget
+ if not attribute and not func:
+ raise NavigationError(
+ 'Must provide either an attribute or a function'
+ )
self._calculate_label()
def _calculate_label(self):
if not self._label:
if self.attribute:
- name, model = SourceColumn.get_attribute_recursive(
- attribute=self.attribute, model=self.source._meta.model
- )
- self._label = label_for_field(
- name=name, model=model
- )
+ try:
+ attribute = resolve_attribute(obj=self.source, attribute=self.attribute)
+ self._label = getattr(attribute, 'short_description')
+ except AttributeError:
+ name, model = SourceColumn.get_attribute_recursive(
+ attribute=self.attribute, model=self.source._meta.model
+ )
+ self._label = label_for_field(
+ name=name, model=model
+ )
else:
- self._label = 'Function'
+ self._label = getattr(self.func, 'short_description', _('Unnamed function'))
self.label = self._label
@@ -564,7 +577,17 @@ class SourceColumn(object):
elif self.func:
result = self.func(context=context, **self.kwargs)
- return result
+ if self.widget:
+ widget_instance = self.widget()
+ return widget_instance.render(name='asd', value=result)
+
+ if not result:
+ if self.empty_value:
+ return self.empty_value
+ else:
+ return result
+ else:
+ return result
class Text(Link):
diff --git a/mayan/apps/navigation/exceptions.py b/mayan/apps/navigation/exceptions.py
new file mode 100644
index 0000000000..ebe73f47de
--- /dev/null
+++ b/mayan/apps/navigation/exceptions.py
@@ -0,0 +1,5 @@
+from __future__ import unicode_literals
+
+
+class NavigationError(Exception):
+ """Base navigation app exception"""
diff --git a/mayan/apps/ocr/apps.py b/mayan/apps/ocr/apps.py
index df13d05c58..816247ea21 100644
--- a/mayan/apps/ocr/apps.py
+++ b/mayan/apps/ocr/apps.py
@@ -110,16 +110,16 @@ class OCRApp(MayanAppConfig):
)
SourceColumn(
- source=DocumentVersionOCRError, label=_('Document'),
- func=lambda context: document_link(context['object'].document_version.document)
+ func=lambda context: document_link(
+ context['object'].document_version.document
+ ), label=_('Document'), source=DocumentVersionOCRError
)
SourceColumn(
- source=DocumentVersionOCRError, label=_('Added'),
- attribute='datetime_submitted'
+ attribute='datetime_submitted', label=_('Date and time'),
+ source=DocumentVersionOCRError
)
SourceColumn(
- source=DocumentVersionOCRError, label=_('Result'),
- attribute='result'
+ attribute='result', source=DocumentVersionOCRError
)
app.conf.task_queues.append(
diff --git a/mayan/apps/permissions/apps.py b/mayan/apps/permissions/apps.py
index 782a225d4f..b442782a0a 100644
--- a/mayan/apps/permissions/apps.py
+++ b/mayan/apps/permissions/apps.py
@@ -11,6 +11,7 @@ from mayan.apps.common import (
menu_secondary, menu_setup
)
from mayan.apps.common.signals import perform_upgrade
+from mayan.apps.navigation import SourceColumn
from .handlers import handler_purge_permissions
from .links import (
@@ -48,6 +49,8 @@ class PermissionsApp(MayanAppConfig):
)
)
+ SourceColumn(attribute='label', is_identifier=True, source=Role)
+
menu_list_facet.bind_links(
links=(
link_acl_list, link_role_groups, link_role_permissions,
diff --git a/mayan/apps/permissions/views.py b/mayan/apps/permissions/views.py
index 4dffa2b508..aa1ac92d52 100644
--- a/mayan/apps/permissions/views.py
+++ b/mayan/apps/permissions/views.py
@@ -199,7 +199,7 @@ class RoleListView(SingleObjectListView):
def get_extra_context(self):
return {
- 'hide_link': True,
+ 'hide_object': True,
'no_results_icon': icon_role_list,
'no_results_main_link': link_role_create.resolve(
context=RequestContext(request=self.request)
diff --git a/mayan/apps/smart_settings/tests/test_classes.py b/mayan/apps/smart_settings/tests/test_classes.py
index 3ec0bbd2ba..55c595102a 100644
--- a/mayan/apps/smart_settings/tests/test_classes.py
+++ b/mayan/apps/smart_settings/tests/test_classes.py
@@ -37,4 +37,3 @@ class ClassesTestCase(BaseTestCase):
setting_value = setting_paginate_by.value
os.environ.pop('MAYAN_{}'.format(TEST_SETTING_NAME))
self.assertEqual(setting_value, TEST_SETTING_VALUE)
-
diff --git a/mayan/apps/tags/apps.py b/mayan/apps/tags/apps.py
index ba403298e4..86adc17f17 100644
--- a/mayan/apps/tags/apps.py
+++ b/mayan/apps/tags/apps.py
@@ -101,7 +101,7 @@ class TagsApp(MayanAppConfig):
)
SourceColumn(
- attribute='label', source=DocumentTag,
+ attribute='label', is_identifier=True, source=DocumentTag,
)
SourceColumn(
attribute='get_preview_widget', source=DocumentTag
diff --git a/mayan/apps/task_manager/apps.py b/mayan/apps/task_manager/apps.py
index 8b92c1dc9b..25231bd785 100644
--- a/mayan/apps/task_manager/apps.py
+++ b/mayan/apps/task_manager/apps.py
@@ -33,16 +33,12 @@ class TaskManagerApp(MayanAppConfig):
source=CeleryQueue, label=_('Name'), attribute='name'
)
SourceColumn(
- source=CeleryQueue, label=_('Default queue?'),
- func=lambda context: TwoStateWidget(
- state=context['object'].is_default_queue
- ).render()
+ attribute='is_default_queue', label=_('Default queue?'),
+ source=CeleryQueue, widget=TwoStateWidget
)
SourceColumn(
- source=CeleryQueue, label=_('Is transient?'),
- func=lambda context: TwoStateWidget(
- state=context['object'].is_transient
- ).render()
+ attribute='is_transient', label=_('Is transient?'),
+ source=CeleryQueue, widget=TwoStateWidget
)
SourceColumn(
source=Task, label=_('Type'), attribute='task_type'
@@ -54,12 +50,12 @@ class TaskManagerApp(MayanAppConfig):
source=Task, label=_('Host'),
func=lambda context: context['object'].kwargs['hostname']
)
- SourceColumn(
- source=Task, label=_('Acknowledged'),
- func=lambda context: TwoStateWidget(
- state=context['object'].kwargs['acknowledged']
- ).render()
- )
+ #SourceColumn(
+ # source=Task, label=_('Acknowledged'),
+ # func=lambda context: TwoStateWidget(
+ # state=context['object'].kwargs['acknowledged']
+ # ).render()
+ #)
SourceColumn(
source=Task, label=_('Arguments'),
func=lambda context: context['object'].kwargs['args']
diff --git a/mayan/apps/user_management/apps.py b/mayan/apps/user_management/apps.py
index 5d07c8ce45..6bf15dd733 100644
--- a/mayan/apps/user_management/apps.py
+++ b/mayan/apps/user_management/apps.py
@@ -31,20 +31,7 @@ from .permissions import (
permission_user_view
)
from .search import * # NOQA
-
-
-def get_groups():
- Group = apps.get_model(app_label='auth', model_name='Group')
- return ','.join([group.name for group in Group.objects.all()])
-
-
-def get_users():
- return ','.join(
- [
- user.get_full_name() or user.username
- for user in get_user_model().objects.all()
- ]
- )
+from .utils import get_groups, get_users
class UserManagementApp(MayanAppConfig):
@@ -89,27 +76,27 @@ class UserManagementApp(MayanAppConfig):
permission_user_view
)
)
+
+ SourceColumn(attribute='name', is_identifier=True, source=Group)
SourceColumn(
- source=Group, label=_('Users'), attribute='user_set.count'
+ attribute='user_set.count', label=_('Users'), source=Group
)
+ SourceColumn(attribute='username', is_identifier=True, source=User)
SourceColumn(
- source=User, label=_('Full name'), attribute='get_full_name'
+ attribute='get_full_name', label=_('Full name'), source=User
+ )
+ SourceColumn(attribute='email', label=_('Email'), source=User)
+ SourceColumn(
+ attribute='is_active', label=_('Active'), source=User,
+ widget=TwoStateWidget
)
SourceColumn(
- source=User, label=_('Email'), attribute='email'
+ attribute='has_usable_password', source=User, widget=TwoStateWidget
)
SourceColumn(
- source=User, label=_('Active'),
- func=lambda context: TwoStateWidget(
- state=context['object'].is_active
- ).render()
- )
- SourceColumn(
- source=User, label=_('Has usable password?'),
- func=lambda context: TwoStateWidget(
- state=context['object'].has_usable_password()
- ).render()
+ attribute='has_usable_password', label=_('Has usable password?'),
+ source=User, widget=TwoStateWidget
)
menu_list_facet.bind_links(
diff --git a/mayan/apps/user_management/utils.py b/mayan/apps/user_management/utils.py
new file mode 100644
index 0000000000..6ea7893fa9
--- /dev/null
+++ b/mayan/apps/user_management/utils.py
@@ -0,0 +1,18 @@
+from __future__ import unicode_literals
+
+from django.apps import apps
+from django.contrib.auth import get_user_model
+
+
+def get_groups():
+ Group = apps.get_model(app_label='auth', model_name='Group')
+ return ','.join([group.name for group in Group.objects.all()])
+
+
+def get_users():
+ return ','.join(
+ [
+ user.get_full_name() or user.username
+ for user in get_user_model().objects.all()
+ ]
+ )
diff --git a/mayan/apps/user_management/views.py b/mayan/apps/user_management/views.py
index 44e537e26f..3285b47969 100644
--- a/mayan/apps/user_management/views.py
+++ b/mayan/apps/user_management/views.py
@@ -55,7 +55,7 @@ class GroupListView(SingleObjectListView):
def get_extra_context(self):
return {
- 'hide_link': True,
+ 'hide_object': True,
'no_results_icon': icon_group_setup,
'no_results_main_link': link_group_create.resolve(
context=RequestContext(request=self.request)
@@ -261,7 +261,7 @@ class UserListView(SingleObjectListView):
def get_extra_context(self):
return {
- 'hide_link': True,
+ 'hide_object': True,
'no_results_icon': icon_user_setup,
'no_results_main_link': link_user_create.resolve(
context=RequestContext(request=self.request)