From 64e1c6bb6720ccdcd9ddf2f06dbaab0e7dd460e4 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Sat, 22 Dec 2018 05:35:31 -0400 Subject: [PATCH] Add widget support to SourceColumn Allow passing a widget class to SourceColumn. This makes using lambdas to render model column unnecesary and are mostly removed too. Signed-off-by: Roberto Rosario --- HISTORY.rst | 3 + .../generic_list_items_subtemplate.html | 2 +- .../appearance/generic_list_subtemplate.html | 2 +- mayan/apps/common/generics.py | 17 ++-- mayan/apps/common/icons.py | 4 +- mayan/apps/common/links.py | 9 +-- mayan/apps/common/literals.py | 6 ++ mayan/apps/common/utils.py | 6 +- mayan/apps/common/views.py | 11 ++- mayan/apps/common/widgets.py | 7 +- mayan/apps/dashboards/apps.py | 1 - mayan/apps/dashboards/classes.py | 6 -- mayan/apps/dashboards/models.py | 6 -- .../templatetags/dashboards_tags.py | 2 +- mayan/apps/dashboards/views.py | 6 -- mayan/apps/django_gpg/apps.py | 22 ++--- mayan/apps/django_gpg/classes.py | 3 + mayan/apps/django_gpg/models.py | 1 + mayan/apps/document_indexing/apps.py | 15 ++-- mayan/apps/document_signatures/handlers.py | 5 +- mayan/apps/document_states/apps.py | 61 +++++--------- mayan/apps/document_states/models.py | 1 + mayan/apps/document_states/views.py | 13 ++- mayan/apps/documents/apps.py | 51 +++++------- mayan/apps/documents/managers.py | 6 +- .../0050_duplicateddocumentproxy.py | 27 +++++++ .../apps/documents/models/document_models.py | 37 ++++++++- .../documents/models/document_type_models.py | 5 +- .../models/document_version_models.py | 5 ++ mayan/apps/documents/views/document_views.py | 18 +---- mayan/apps/documents/widgets.py | 9 --- mayan/apps/events/apps.py | 51 ++++++------ mayan/apps/events/links.py | 4 - mayan/apps/events/views.py | 26 ------ mayan/apps/events/widgets.py | 21 ++++- mayan/apps/file_metadata/apps.py | 11 +-- .../migrations/0002_auto_20181222_0931.py | 25 ++++++ mayan/apps/linking/apps.py | 18 ++--- mayan/apps/linking/views.py | 2 +- mayan/apps/mailer/apps.py | 26 ++---- mayan/apps/mailer/models.py | 3 +- mayan/apps/metadata/api.py | 80 +++++++++---------- mayan/apps/metadata/apps.py | 26 +++--- mayan/apps/metadata/models.py | 1 + mayan/apps/metadata/views.py | 10 +-- mayan/apps/metadata/widgets.py | 13 ++- mayan/apps/motd/apps.py | 13 +-- mayan/apps/motd/views.py | 2 +- mayan/apps/navigation/classes.py | 41 +++++++--- mayan/apps/navigation/exceptions.py | 5 ++ mayan/apps/ocr/apps.py | 12 +-- mayan/apps/permissions/apps.py | 3 + mayan/apps/permissions/views.py | 2 +- .../apps/smart_settings/tests/test_classes.py | 1 - mayan/apps/tags/apps.py | 2 +- mayan/apps/task_manager/apps.py | 24 +++--- mayan/apps/user_management/apps.py | 41 ++++------ mayan/apps/user_management/utils.py | 18 +++++ mayan/apps/user_management/views.py | 4 +- 59 files changed, 450 insertions(+), 402 deletions(-) delete mode 100644 mayan/apps/dashboards/models.py delete mode 100644 mayan/apps/dashboards/views.py create mode 100644 mayan/apps/documents/migrations/0050_duplicateddocumentproxy.py create mode 100644 mayan/apps/file_metadata/migrations/0002_auto_20181222_0931.py create mode 100644 mayan/apps/navigation/exceptions.py create mode 100644 mayan/apps/user_management/utils.py 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)