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 <Roberto.Rosario@mayan-edms.com>
This commit is contained in:
Roberto Rosario
2018-12-22 05:35:31 -04:00
parent 360e756093
commit 64e1c6bb67
59 changed files with 450 additions and 402 deletions

View File

@@ -211,6 +211,9 @@
clicking on the title of the body of the card, not just on clicking on the title of the body of the card, not just on
the checkbox next to the title. the checkbox next to the title.
- Event handler to highlight panels when selected. - Event handler to highlight panels when selected.
- Improve duplicated document display.
- Filter document duplicted count by access.
3.1.9 (2018-11-01) 3.1.9 (2018-11-01)
================== ==================

View File

@@ -76,7 +76,7 @@
{{ object }} {{ object }}
{% endif %} {% endif %}
{% else %} {% 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 %} {% endif %}
</span> </span>
</label> </label>

View File

@@ -98,7 +98,7 @@
{% endif %} {% endif %}
{% if not hide_columns %} {% if not hide_columns %}
{% for column in object|get_source_columns %} {% for column in object|get_source_columns %}
<td>{% source_column_resolve column=column %}{{ column_result }}</td> <td>{% source_column_resolve column=column as column_value %}{{ column_value }}</td>{# Use explicit 'as column_value ' to force date rendering #}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% for column in extra_columns %} {% for column in extra_columns %}

View File

@@ -23,6 +23,10 @@ from pure_pagination.mixins import PaginationMixin
from .forms import ChoiceForm from .forms import ChoiceForm
from .icons import icon_assign_remove_add, icon_assign_remove_remove 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 ( from .mixins import (
DeleteExtraDataMixin, DynamicFormViewMixin, ExtraContextMixin, DeleteExtraDataMixin, DynamicFormViewMixin, ExtraContextMixin,
FormExtraKwargsMixin, MultipleObjectMixin, ObjectActionMixin, FormExtraKwargsMixin, MultipleObjectMixin, ObjectActionMixin,
@@ -500,14 +504,17 @@ class SingleObjectListView(PaginationMixin, ViewPermissionCheckMixin, ObjectList
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(SingleObjectListView, self).get_context_data(**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: 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 return context
def get_paginate_by(self, queryset): def get_paginate_by(self, queryset):

View File

@@ -30,8 +30,8 @@ icon_menu_about = Icon(
icon_menu_user = Icon( icon_menu_user = Icon(
driver_name='fontawesome', symbol='user-circle' driver_name='fontawesome', symbol='user-circle'
) )
icon_object_error_list_with_icon = Icon( icon_object_error_list = Icon(
driver_name='fontawesome', symbol='lock' driver_name='fontawesome', symbol='exclamation-triangle'
) )
icon_ok = Icon( icon_ok = Icon(
driver_name='fontawesome', symbol='check' driver_name='fontawesome', symbol='check'

View File

@@ -10,7 +10,7 @@ from .icons import (
icon_about, icon_check_version, icon_current_user_details, icon_about, icon_check_version, icon_current_user_details,
icon_current_user_edit, icon_current_user_locale_profile_details, icon_current_user_edit, icon_current_user_locale_profile_details,
icon_current_user_locale_profile_edit, icon_documentation, icon_forum, 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 icon_setup, icon_source_code, icon_support, icon_tools
) )
from .permissions_runtime import permission_error_log_view from .permissions_runtime import permission_error_log_view
@@ -65,6 +65,7 @@ link_documentation = Link(
text=_('Documentation'), url='https://docs.mayan-edms.com' text=_('Documentation'), url='https://docs.mayan-edms.com'
) )
link_object_error_list = Link( link_object_error_list = Link(
icon_class=icon_object_error_list,
kwargs=get_kwargs_factory('resolved_object'), kwargs=get_kwargs_factory('resolved_object'),
permissions=(permission_error_log_view,), text=_('Errors'), permissions=(permission_error_log_view,), text=_('Errors'),
view='common:object_error_list', view='common:object_error_list',
@@ -74,12 +75,6 @@ link_object_error_list_clear = Link(
permissions=(permission_error_log_view,), text=_('Clear all'), permissions=(permission_error_log_view,), text=_('Clear all'),
view='common:object_error_list_clear', 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( link_forum = Link(
icon_class=icon_forum, tags='new_window', text=_('Forum'), icon_class=icon_forum, tags='new_window', text=_('Forum'),
url='https://forum.mayan-edms.com' url='https://forum.mayan-edms.com'

View File

@@ -11,6 +11,12 @@ MESSAGE_SQLITE_WARNING = _(
'for development and testing, not for production.' 'for development and testing, not for production.'
) )
PYPI_URL = 'https://pypi.python.org/pypi' 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_DAYS = 'days'
TIME_DELTA_UNIT_HOURS = 'hours' TIME_DELTA_UNIT_HOURS = 'hours'
TIME_DELTA_UNIT_MINUTES = 'minutes' TIME_DELTA_UNIT_MINUTES = 'minutes'

View File

@@ -4,7 +4,6 @@ import logging
import os import os
import shutil import shutil
import tempfile import tempfile
import types
from django.conf import settings from django.conf import settings
from django.db.models.constants import LOOKUP_SEP 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 _ from django.utils.translation import ugettext_lazy as _
import mayan import mayan
from mayan.apps.common.compat import dict_type, dictionary_type
from .exceptions import NotLatestVersion, UnknownLatestVersion from .exceptions import NotLatestVersion, UnknownLatestVersion
from .literals import DJANGO_SQLITE_BACKEND, MAYAN_PYPI_NAME, PYPI_URL from .literals import DJANGO_SQLITE_BACKEND, MAYAN_PYPI_NAME, PYPI_URL
@@ -155,9 +153,9 @@ def resolve_attribute(obj, attribute, kwargs=None):
except AttributeError: except AttributeError:
# Try as a related model field # Try as a related model field
if LOOKUP_SEP in attribute: if LOOKUP_SEP in attribute:
attrib = attribute.replace(LOOKUP_SEP, '.') attribute_replaced = attribute.replace(LOOKUP_SEP, '.')
return resolve_attribute( return resolve_attribute(
obj=obj, attribute=attribute, kwargs=kwargs obj=obj, attribute=attribute_replaced, kwargs=kwargs
) )
else: else:
raise raise

View File

@@ -28,7 +28,7 @@ from .generics import ( # NOQA
SingleObjectDownloadView, SingleObjectDynamicFormCreateView, SingleObjectDownloadView, SingleObjectDynamicFormCreateView,
SingleObjectDynamicFormEditView, SingleObjectEditView, SingleObjectListView 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 .menus import menu_setup, menu_tools
from .permissions_runtime import permission_error_log_view from .permissions_runtime import permission_error_log_view
from .settings import setting_home_view from .settings import setting_home_view
@@ -211,6 +211,15 @@ class ObjectErrorLogEntryListView(SingleObjectListView):
{'name': _('Result'), 'attribute': 'result'}, {'name': _('Result'), 'attribute': 'result'},
), ),
'hide_object': True, '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(), 'object': self.get_object(),
'title': _('Error log entries for: %s' % self.get_object()), 'title': _('Error log entries for: %s' % self.get_object()),
} }

View File

@@ -89,18 +89,17 @@ class TextAreaDiv(forms.widgets.Widget):
class TwoStateWidget(object): class TwoStateWidget(object):
def __init__(self, state, center=False, icon_ok=None, icon_fail=None): def __init__(self, center=False, icon_ok=None, icon_fail=None):
self.state = state
self.icon_ok = icon_ok or default_icon_ok self.icon_ok = icon_ok or default_icon_ok
self.icon_fail = icon_fail or default_icon_fail self.icon_fail = icon_fail or default_icon_fail
self.center = center self.center = center
def render(self): def render(self, name=None, value=None):
center_class = '' center_class = ''
if self.center: if self.center:
center_class = 'text-center' center_class = 'text-center'
if self.state: if value:
return mark_safe( return mark_safe(
'<div class="{} text-success">{}</div>'.format( '<div class="{} text-success">{}</div>'.format(
center_class, self.icon_ok.render() center_class, self.icon_ok.render()

View File

@@ -1,6 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.apps import apps
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from mayan.apps.common import MayanAppConfig from mayan.apps.common import MayanAppConfig

View File

@@ -1,12 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.apps import apps
from django.db import models
from django.template import loader 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): class Dashboard(object):

View File

@@ -1,6 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models
# Create your models here.

View File

@@ -1,6 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.template import Context, Library from django.template import Library
from ..classes import Dashboard from ..classes import Dashboard

View File

@@ -1,6 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.shortcuts import render
# Create your views here.

View File

@@ -44,22 +44,24 @@ class DjangoGPGApp(MayanAppConfig):
) )
) )
SourceColumn(source=Key, label=_('Key ID'), attribute='key_id') SourceColumn(attribute='key_id', is_identifier=True, source=Key)
SourceColumn(source=Key, label=_('User ID'), attribute='user_id') SourceColumn(attribute='user_id', source=Key)
SourceColumn(source=KeyStub, label=_('Key ID'), attribute='key_id')
SourceColumn(source=KeyStub, label=_('Type'), attribute='key_type')
SourceColumn( 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( SourceColumn(
source=KeyStub, label=_('Expiration date'), attribute='expires', empty_value=_('No expiration'),
func=lambda context: context['object'].expires or _('No expiration') label=_('Expiration date'), source=KeyStub
) )
SourceColumn(source=KeyStub, label=_('Length'), attribute='length') SourceColumn(attribute='length', label=_('Length'), source=KeyStub)
SourceColumn( 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,)) menu_object.bind_links(links=(link_key_detail,), sources=(Key,))

View File

@@ -6,6 +6,8 @@ import shutil
import gnupg import gnupg
from django.utils.translation import ugettext_lazy as _
from mayan.apps.common.utils import mkdtemp from mayan.apps.common.utils import mkdtemp
@@ -149,6 +151,7 @@ class KeyStub(object):
@property @property
def key_id(self): def key_id(self):
return self.fingerprint[-8:] return self.fingerprint[-8:]
key_id.fget.short_description = _('Key ID')
class SignatureVerification(object): class SignatureVerification(object):

View File

@@ -85,6 +85,7 @@ class Key(models.Model):
Short form key ID (using the first 8 characters). Short form key ID (using the first 8 characters).
""" """
return self.fingerprint[-8:] return self.fingerprint[-8:]
key_id.fget.short_description = _('Key ID')
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
import_results, key_info = gpg_backend.import_and_list_keys( import_results, key_info = gpg_backend.import_and_list_keys(

View File

@@ -87,9 +87,7 @@ class DocumentIndexingApp(MayanAppConfig):
SourceColumn(attribute='label', is_identifier=True, source=Index) SourceColumn(attribute='label', is_identifier=True, source=Index)
SourceColumn(attribute='slug', source=Index) SourceColumn(attribute='slug', source=Index)
SourceColumn( SourceColumn(
func=lambda context: TwoStateWidget( attribute='enabled', source=Index, widget=TwoStateWidget
state=context['object'].enabled
).render(), label=_('Enabled'), source=Index
) )
SourceColumn( SourceColumn(
@@ -111,15 +109,12 @@ class DocumentIndexingApp(MayanAppConfig):
label=_('Level'), source=IndexTemplateNode label=_('Level'), source=IndexTemplateNode
) )
SourceColumn( SourceColumn(
func=lambda context: TwoStateWidget( attribute='enabled', source=IndexTemplateNode,
state=context['object'].enabled widget=TwoStateWidget
).render(), label=_('Enabled'), source=IndexTemplateNode
) )
SourceColumn( SourceColumn(
func=lambda context: TwoStateWidget( attribute='link_documents', source=IndexTemplateNode,
state=context['object'].link_documents widget=TwoStateWidget
).render(), label=_('Has document links?'),
source=IndexTemplateNode,
) )
SourceColumn( SourceColumn(

View File

@@ -1,9 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from .tasks import ( from .tasks import task_unverify_key_signatures, task_verify_key_signatures
task_unverify_key_signatures, task_verify_key_signatures,
task_verify_missing_embedded_signature
)
def handler_unverify_key_signatures(sender, **kwargs): def handler_unverify_key_signatures(sender, **kwargs):

View File

@@ -135,16 +135,14 @@ class DocumentStatesApp(MayanAppConfig):
SourceColumn( SourceColumn(
attribute='label', is_identifier=True, source=Workflow attribute='label', is_identifier=True, source=Workflow
) )
SourceColumn( SourceColumn(attribute='internal_name', source=Workflow)
attribute='internal_name', source=Workflow SourceColumn(attribute='get_initial_state', source=Workflow)
)
SourceColumn(
attribute='get_initial_state', source=Workflow,
)
SourceColumn( SourceColumn(
attribute='get_current_state', source=WorkflowInstance attribute='workflow', is_identifier=True,
source=WorkflowInstance
) )
SourceColumn(attribute='get_current_state', source=WorkflowInstance)
SourceColumn( SourceColumn(
attribute='get_last_transition_user', source=WorkflowInstance attribute='get_last_transition_user', source=WorkflowInstance
) )
@@ -163,57 +161,40 @@ class DocumentStatesApp(MayanAppConfig):
SourceColumn( SourceColumn(
attribute='get_rendered_datetime', source=WorkflowInstanceLogEntry attribute='get_rendered_datetime', source=WorkflowInstanceLogEntry
) )
SourceColumn( SourceColumn(attribute='user', source=WorkflowInstanceLogEntry)
attribute='user', source=WorkflowInstanceLogEntry SourceColumn(attribute='transition', source=WorkflowInstanceLogEntry)
) SourceColumn(attribute='comment', source=WorkflowInstanceLogEntry)
SourceColumn(
attribute='transition', source=WorkflowInstanceLogEntry
)
SourceColumn(
attribute='comment', source=WorkflowInstanceLogEntry
)
SourceColumn( SourceColumn(
source=WorkflowState, label=_('Is initial state?'), attribute='label', is_identifier=True, source=WorkflowState
func=lambda context: TwoStateWidget(
state=context['object'].initial
).render()
) )
SourceColumn( SourceColumn(
source=WorkflowState, label=_('Completion'), attribute='completion' attribute='initial', source=WorkflowState, widget=TwoStateWidget
) )
SourceColumn(attribute='completion', source=WorkflowState)
SourceColumn( SourceColumn(
source=WorkflowStateAction, label=_('Label'), attribute='label' attribute='label', is_identifier=True, source=WorkflowStateAction
) )
SourceColumn( SourceColumn(
source=WorkflowStateAction, label=_('Enabled?'), attribute='enabled', source=WorkflowStateAction,
func=lambda context: TwoStateWidget( widget=TwoStateWidget
state=context['object'].enabled
).render()
) )
SourceColumn( SourceColumn(
source=WorkflowStateAction, label=_('When?'), attribute='get_when_display', label=_('When?'),
attribute='get_when_display' source=WorkflowStateAction
)
SourceColumn(
source=WorkflowStateAction, label=_('Action type'),
attribute='get_class_label'
) )
SourceColumn(attribute='get_class_label', source=WorkflowStateAction)
SourceColumn( SourceColumn(
source=WorkflowTransition, label=_('Origin state'), attribute='label', is_identifier=True, source=WorkflowTransition
attribute='origin_state'
) )
SourceColumn(attribute='origin_state', source=WorkflowTransition)
SourceColumn(attribute='destination_state', source=WorkflowTransition)
SourceColumn( SourceColumn(
source=WorkflowTransition, label=_('Destination state'),
attribute='destination_state'
)
SourceColumn(
source=WorkflowTransition, label=_('Triggers'),
func=lambda context: widget_transition_events( func=lambda context: widget_transition_events(
transition=context['object'] transition=context['object']
) ), label=_('Triggers'), source=WorkflowTransition
) )
app.conf.task_queues.extend( app.conf.task_queues.extend(

View File

@@ -318,6 +318,7 @@ class WorkflowStateAction(models.Model):
def get_class_label(self): def get_class_label(self):
return self.get_class().label return self.get_class().label
get_class_label.short_description = _('Action type')
def loads(self): def loads(self):
return json.loads(self.action_data) return json.loads(self.action_data)

View File

@@ -62,7 +62,7 @@ class DocumentWorkflowInstanceListView(SingleObjectListView):
def get_extra_context(self): def get_extra_context(self):
return { return {
'hide_link': True, 'hide_object': True,
'no_results_icon': icon_workflow_list, 'no_results_icon': icon_workflow_list,
'no_results_text': _( 'no_results_text': _(
'Assign workflows to the document type of this document ' 'Assign workflows to the document type of this document '
@@ -96,6 +96,13 @@ class WorkflowInstanceDetailView(SingleObjectListView):
return { return {
'hide_object': True, 'hide_object': True,
'navigation_object_list': ('object', 'workflow_instance'), '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, 'object': self.get_workflow_instance().document,
'title': _('Detail of workflow: %(workflow)s') % { 'title': _('Detail of workflow: %(workflow)s') % {
'workflow': self.get_workflow_instance() 'workflow': self.get_workflow_instance()
@@ -509,7 +516,7 @@ class SetupWorkflowStateListView(SingleObjectListView):
def get_extra_context(self): def get_extra_context(self):
return { return {
'hide_link': True, 'hide_object': True,
'no_results_icon': icon_workflow_state, 'no_results_icon': icon_workflow_state,
'no_results_main_link': link_setup_workflow_state_create.resolve( 'no_results_main_link': link_setup_workflow_state_create.resolve(
context=RequestContext( context=RequestContext(
@@ -624,7 +631,7 @@ class SetupWorkflowTransitionListView(SingleObjectListView):
def get_extra_context(self): def get_extra_context(self):
return { return {
'hide_link': True, 'hide_object': True,
'no_results_icon': icon_workflow_transition, 'no_results_icon': icon_workflow_transition,
'no_results_main_link': link_setup_workflow_transition_create.resolve( 'no_results_main_link': link_setup_workflow_transition_create.resolve(
context=RequestContext( context=RequestContext(

View File

@@ -104,10 +104,7 @@ from .queues import * # NOQA
from .search import document_page_search, document_search # NOQA from .search import document_page_search, document_search # NOQA
from .signals import post_version_upload from .signals import post_version_upload
from .statistics import * # NOQA from .statistics import * # NOQA
from .widgets import ( from .widgets import DocumentPageThumbnailWidget
DocumentPageThumbnailWidget, widget_document_page_number,
widget_document_version_page_number
)
class DocumentsApp(MayanAppConfig): class DocumentsApp(MayanAppConfig):
@@ -129,7 +126,7 @@ class DocumentsApp(MayanAppConfig):
DocumentType = self.get_model('DocumentType') DocumentType = self.get_model('DocumentType')
DocumentTypeFilename = self.get_model('DocumentTypeFilename') DocumentTypeFilename = self.get_model('DocumentTypeFilename')
DocumentVersion = self.get_model('DocumentVersion') DocumentVersion = self.get_model('DocumentVersion')
DuplicatedDocument = self.get_model('DuplicatedDocument') DuplicatedDocumentProxy = self.get_model('DuplicatedDocumentProxy')
DynamicSerializerField.add_serializer( DynamicSerializerField.add_serializer(
klass=Document, klass=Document,
@@ -244,9 +241,7 @@ class DocumentsApp(MayanAppConfig):
SourceColumn( SourceColumn(
attribute='document_type', label=_('Type'), source=Document attribute='document_type', label=_('Type'), source=Document
) )
SourceColumn( SourceColumn(attribute='get_page_count', source=Document)
func=widget_document_page_number, label=_('Pages'), source=Document
)
# DocumentPage # DocumentPage
SourceColumn( SourceColumn(
@@ -274,9 +269,8 @@ class DocumentsApp(MayanAppConfig):
) )
SourceColumn( SourceColumn(
func=lambda context: TwoStateWidget( attribute='enabled', source=DocumentTypeFilename,
state=context['object'].enabled widget=TwoStateWidget
).render(), label=_('Enabled'), source=DocumentTypeFilename
) )
# DeletedDocument # DeletedDocument
@@ -290,41 +284,34 @@ class DocumentsApp(MayanAppConfig):
attribute='document_type', source=DeletedDocument attribute='document_type', source=DeletedDocument
) )
SourceColumn( SourceColumn(
attribute='deleted_date_time', source=DeletedDocument attribute='get_rendered_deleted_date_time', source=DeletedDocument
) )
# DocumentVersion # DocumentVersion
SourceColumn(
attribute='get_rendered_timestamp', is_identifier=True,
source=DocumentVersion
)
SourceColumn( SourceColumn(
func=lambda context: document_page_thumbnail_widget.render( func=lambda context: document_page_thumbnail_widget.render(
instance=context['object'] instance=context['object']
), label=_('Thumbnail'), source=DocumentVersion ), label=_('Thumbnail'), source=DocumentVersion
) )
SourceColumn( SourceColumn(attribute='get_page_count', source=DocumentVersion)
attribute='timestamp', source=DocumentVersion SourceColumn(attribute='mimetype', source=DocumentVersion)
) SourceColumn(attribute='encoding', source=DocumentVersion)
SourceColumn( SourceColumn(attribute='comment', source=DocumentVersion)
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
)
# DuplicatedDocument # DuplicatedDocument
SourceColumn( SourceColumn(
func=lambda context: document_page_thumbnail_widget.render( func=lambda context: document_page_thumbnail_widget.render(
instance=context['object'].document instance=context['object']
), label=_('Thumbnail'), source=DuplicatedDocument ), label=_('Thumbnail'), source=DuplicatedDocumentProxy
) )
SourceColumn( SourceColumn(
func=lambda context: context['object'].documents.count(), func=lambda context: context['object'].get_duplicate_count(
label=_('Duplicates'), source=DuplicatedDocument user=context['request'].user,
), label=_('Duplicates'), source=DuplicatedDocumentProxy
) )
app.conf.beat_schedule.update( app.conf.beat_schedule.update(

View File

@@ -128,10 +128,10 @@ class DuplicatedDocumentManager(models.Manager):
self.filter(documents=None).delete() self.filter(documents=None).delete()
def get_duplicated_documents(self): def get_duplicated_documents(self):
Document = apps.get_model( DuplicatedDocumentProxy = apps.get_model(
app_label='documents', model_name='Document' app_label='documents', model_name='DuplicatedDocumentProxy'
) )
return Document.objects.filter( return DuplicatedDocumentProxy.objects.filter(
pk__in=self.filter(documents__isnull=False).values_list( pk__in=self.filter(documents__isnull=False).values_list(
'document_id', flat=True 'document_id', flat=True
) )

View File

@@ -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',),
),
]

View File

@@ -7,12 +7,15 @@ from django.apps import apps
from django.conf import settings from django.conf import settings
from django.core.files import File from django.core.files import File
from django.db import models from django.db import models
from django.template import Context, Template
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import force_text, python_2_unicode_compatible from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import ugettext from django.utils.translation import ugettext
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from mayan.apps.acls.models import AccessControlList
from ..events import ( from ..events import (
event_document_create, event_document_properties_edit, event_document_create, event_document_properties_edit,
event_document_type_change, event_document_type_change,
@@ -21,6 +24,7 @@ from ..managers import (
DocumentManager, DuplicatedDocumentManager, FavoriteDocumentManager, DocumentManager, DuplicatedDocumentManager, FavoriteDocumentManager,
PassthroughManager, RecentDocumentManager, TrashCanManager PassthroughManager, RecentDocumentManager, TrashCanManager
) )
from ..permissions import permission_document_view
from ..settings import setting_language from ..settings import setting_language
from ..signals import post_document_type_change from ..signals import post_document_type_change
@@ -77,7 +81,6 @@ class Document(models.Model):
'Whether or not this document is in the trash.' 'Whether or not this document is in the trash.'
), editable=False, verbose_name=_('In trash?') ), editable=False, verbose_name=_('In trash?')
) )
# TODO: set editable to False
deleted_date_time = models.DateTimeField( deleted_date_time = models.DateTimeField(
blank=True, editable=True, help_text=_( blank=True, editable=True, help_text=_(
'The server date and time when the document was moved to the ' 'The server date and time when the document was moved to the '
@@ -139,6 +142,24 @@ class Document(models.Model):
if latest_version: if latest_version:
return latest_version.get_api_image_url(*args, **kwargs) 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): def invalidate_cache(self):
for document_version in self.versions.all(): for document_version in self.versions.all():
document_version.invalidate_cache() document_version.invalidate_cache()
@@ -285,6 +306,20 @@ class DuplicatedDocument(models.Model):
return force_text(self.document) 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 @python_2_unicode_compatible
class FavoriteDocument(models.Model): class FavoriteDocument(models.Model):
""" """

View File

@@ -87,10 +87,11 @@ class DocumentType(models.Model):
def get_document_count(self, user): def get_document_count(self, user):
queryset = AccessControlList.objects.filter_by_access( 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() return queryset.count()
get_document_count.short_description = _('Documents')
def natural_key(self): def natural_key(self):
return (self.label,) return (self.label,)

View File

@@ -181,6 +181,10 @@ class DocumentVersion(models.Model):
) )
raise raise
def get_page_count(self):
return self.pages.count()
get_page_count.short_description = _('Pages')
def get_rendered_string(self, preserve_extension=False): def get_rendered_string(self, preserve_extension=False):
if preserve_extension: if preserve_extension:
filename, extension = os.path.splitext(self.document.label) filename, extension = os.path.splitext(self.document.label)
@@ -196,6 +200,7 @@ class DocumentVersion(models.Model):
return Template('{{ instance.timestamp }}').render( return Template('{{ instance.timestamp }}').render(
context=Context({'instance': self}) context=Context({'instance': self})
) )
get_rendered_timestamp.short_description = _('Date and time')
def natural_key(self): def natural_key(self):
return (self.checksum, self.document.natural_key()) return (self.checksum, self.document.natural_key())

View File

@@ -20,7 +20,6 @@ from mayan.apps.common.generics import (
SingleObjectDownloadView, SingleObjectEditView, SingleObjectListView SingleObjectDownloadView, SingleObjectEditView, SingleObjectListView
) )
from mayan.apps.common.mixins import MultipleInstanceActionMixin 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.models import Transformation
from mayan.apps.converter.permissions import ( from mayan.apps.converter.permissions import (
permission_transformation_delete, permission_transformation_edit permission_transformation_delete, permission_transformation_edit
@@ -259,12 +258,7 @@ class DocumentDuplicatesListView(DocumentListView):
return context return context
def get_object_list(self): def get_object_list(self):
try: return self.get_document().get_duplicates()
return DuplicatedDocument.objects.get(
document=self.get_document()
).documents.all()
except DuplicatedDocument.DoesNotExist:
return Document.objects.none()
class DocumentEditView(SingleObjectEditView): class DocumentEditView(SingleObjectEditView):
@@ -842,16 +836,6 @@ class DuplicatedDocumentListView(DocumentListView):
context = super(DuplicatedDocumentListView, self).get_extra_context() context = super(DuplicatedDocumentListView, self).get_extra_context()
context.update( 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_icon': icon_duplicated_document_list,
'no_results_text': _( 'no_results_text': _(
'Duplicates are documents that are composed of the exact ' 'Duplicates are documents that are composed of the exact '

View File

@@ -3,7 +3,6 @@ from __future__ import unicode_literals
from django import forms from django import forms
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from .settings import ( from .settings import (
setting_display_height, setting_display_width, setting_preview_height, setting_display_height, setting_display_width, setting_preview_height,
@@ -75,11 +74,3 @@ def document_link(document):
return mark_safe('<a href="%s">%s</a>' % ( return mark_safe('<a href="%s">%s</a>' % (
document.get_absolute_url(), document) 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()

View File

@@ -18,7 +18,9 @@ from .links import (
link_notification_mark_read_all, link_user_events, link_notification_mark_read_all, link_user_events,
link_user_notifications_list 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): class EventsApp(MayanAppConfig):
@@ -37,21 +39,22 @@ class EventsApp(MayanAppConfig):
User = get_user_model() User = get_user_model()
SourceColumn( SourceColumn(
source=Action, label=_('Timestamp'), attribute='timestamp' attribute='timestamp', is_identifier=True,
label=_('Date and time'), source=Action
) )
SourceColumn( SourceColumn(
source=Action, label=_('Actor'), func=widget_event_user_link, label=_('Actor'), source=Action
func=lambda context: event_user_link(context['object'])
) )
SourceColumn( SourceColumn(
source=Action, label=_('Event'), func=widget_event_type_link, label=_('Event'), source=Action
func=lambda context: event_type_link(context['object'])
) )
SourceColumn( SourceColumn(
source=Action, label=_('Action object'), func=widget_event_object_link, kwargs={
func=lambda context: event_object_link( 'attribute': 'action_object'
entry=context['object'], attribute='action_object' }, label=_('Action object'), source=Action
) )
SourceColumn(
func=widget_event_object_link, label=_('Target'), source=Action
) )
SourceColumn( SourceColumn(
@@ -62,25 +65,27 @@ class EventsApp(MayanAppConfig):
) )
SourceColumn( SourceColumn(
source=Notification, label=_('Timestamp'), attribute='action.timestamp', is_identifier=True,
attribute='action.timestamp' label=_('Date and time'), source=Notification
) )
SourceColumn( SourceColumn(
source=Notification, label=_('Actor'), attribute='action.actor' func=widget_event_user_link, kwargs={'attribute': 'action'},
label=_('Actor'), source=Notification
) )
SourceColumn( SourceColumn(
source=Notification, label=_('Event'), func=widget_event_type_link, kwargs={'attribute': 'action'},
func=lambda context: event_type_link(context['object'].action) label=_('Event'), source=Notification
)
SourceColumn(
func=widget_event_object_link, kwargs={
'attribute': 'action.target'
}, label=_('Target'), source=Notification
) )
SourceColumn( SourceColumn(
source=Notification, label=_('Target'), attribute='read', label=_('Seen'), source=Notification,
func=lambda context: event_object_link(context['object'].action) widget=TwoStateWidget
)
SourceColumn(
source=Notification, label=_('Seen'),
func=lambda context: TwoStateWidget(
state=context['object'].read
).render()
) )
menu_list_facet.bind_links( menu_list_facet.bind_links(

View File

@@ -32,10 +32,6 @@ def get_kwargs_factory(variable_name):
return get_kwargs return get_kwargs
def get_notification_count(context):
return context['request'].user.notifications.filter(read=False).count()
link_current_user_events = Link( link_current_user_events = Link(
icon_class=icon_events_user_list, text=_('My events'), icon_class=icon_events_user_list, text=_('My events'),
view='events:current_user_events' view='events:current_user_events'

View File

@@ -13,7 +13,6 @@ from actstream.models import Action, any_stream
from mayan.apps.acls.models import AccessControlList from mayan.apps.acls.models import AccessControlList
from mayan.apps.common.generics import FormView, SimpleView from mayan.apps.common.generics import FormView, SimpleView
from mayan.apps.common.utils import encapsulate
from mayan.apps.common.views import SingleObjectListView from mayan.apps.common.views import SingleObjectListView
from .classes import EventType, ModelEventType from .classes import EventType, ModelEventType
@@ -26,7 +25,6 @@ from .icons import (
from .links import link_event_types_subscriptions_list from .links import link_event_types_subscriptions_list
from .models import StoredEventType from .models import StoredEventType
from .permissions import permission_events_view from .permissions import permission_events_view
from .widgets import event_object_link
class EventListView(SingleObjectListView): class EventListView(SingleObjectListView):
@@ -34,14 +32,6 @@ class EventListView(SingleObjectListView):
def get_extra_context(self): def get_extra_context(self):
return { return {
'extra_columns': (
{
'name': _('Target'),
'attribute': encapsulate(
lambda entry: event_object_link(entry)
)
},
),
'hide_object': True, 'hide_object': True,
'title': _('Events'), 'title': _('Events'),
} }
@@ -270,14 +260,6 @@ class UserEventListView(SingleObjectListView):
def get_extra_context(self): def get_extra_context(self):
return { return {
'extra_columns': (
{
'name': _('Target'),
'attribute': encapsulate(
lambda entry: event_object_link(entry)
)
},
),
'hide_object': True, 'hide_object': True,
'no_results_icon': icon_events_user_list, 'no_results_icon': icon_events_user_list,
'no_results_text': _( 'no_results_text': _(
@@ -308,14 +290,6 @@ class CurrentUserEventListView(UserEventListView):
class VerbEventListView(SingleObjectListView): class VerbEventListView(SingleObjectListView):
def get_extra_context(self): def get_extra_context(self):
return { return {
'extra_columns': (
{
'name': _('Target'),
'attribute': encapsulate(
lambda entry: event_object_link(entry)
)
},
),
'hide_object': True, 'hide_object': True,
'title': _( 'title': _(
'Events of type: %s' 'Events of type: %s'

View File

@@ -5,15 +5,18 @@ from django.utils.encoding import force_text
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from mayan.apps.common.utils import resolve_attribute
from .classes import EventType from .classes import EventType
def event_object_link(entry, attribute='target'): def widget_event_object_link(context, attribute='target'):
entry = context['object']
label = '' label = ''
url = '#' url = '#'
obj_type = '' obj_type = ''
obj = getattr(entry, attribute) obj = resolve_attribute(obj=entry, attribute=attribute)
if obj: if obj:
obj_type = '{}: '.format(obj._meta.verbose_name) 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( return mark_safe(
'<a href="%(url)s">%(label)s</a>' % { '<a href="%(url)s">%(label)s</a>' % {
'url': reverse('events:events_by_verb', kwargs={'verb': entry.verb}), '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: if entry.actor == entry.target:
return _('System') return _('System')
else: else:

View File

@@ -121,16 +121,17 @@ class FileMetadataApp(MayanAppConfig):
model=DocumentTypeSettings, related='document_type', model=DocumentTypeSettings, related='document_type',
) )
SourceColumn(source=FileMetadataEntry, attribute='key') SourceColumn(attribute='key', source=FileMetadataEntry)
SourceColumn(source=FileMetadataEntry, attribute='value') SourceColumn(attribute='value', source=FileMetadataEntry)
SourceColumn( SourceColumn(
source=DocumentVersionDriverEntry, attribute='driver' attribute='driver', source=DocumentVersionDriverEntry
) )
SourceColumn( SourceColumn(
source=DocumentVersionDriverEntry, attribute='driver__internal_name' attribute='driver__internal_name',
source=DocumentVersionDriverEntry
) )
SourceColumn( SourceColumn(
source=DocumentVersionDriverEntry, attribute='get_attribute_count' attribute='get_attribute_count', source=DocumentVersionDriverEntry
) )
app.conf.task_queues.append( app.conf.task_queues.append(

View File

@@ -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'),
),
]

View File

@@ -57,28 +57,22 @@ class LinkingApp(MayanAppConfig):
) )
SourceColumn( SourceColumn(
source=ResolvedSmartLink, label=_('Label'),
func=lambda context: context['object'].get_label_for( func=lambda context: context['object'].get_label_for(
document=context['document'] document=context['document']
) ), label=_('Label'), source=ResolvedSmartLink
) )
SourceColumn( SourceColumn(
source=SmartLink, label=_('Dynamic label'), attribute='label', is_identifier=True, source=SmartLink
attribute='dynamic_label'
) )
SourceColumn(attribute='dynamic_label', source=SmartLink)
SourceColumn( SourceColumn(
source=SmartLink, label=_('Enabled'), attribute='enabled', source=SmartLink, widget=TwoStateWidget
func=lambda context: TwoStateWidget(
state=context['object'].enabled
).render()
) )
SourceColumn( SourceColumn(
source=SmartLinkCondition, label=_('Enabled'), attribute='enabled', source=SmartLinkCondition,
func=lambda context: TwoStateWidget( widget=TwoStateWidget
state=context['object'].enabled
).render()
) )
menu_facet.bind_links( menu_facet.bind_links(

View File

@@ -139,7 +139,7 @@ class SmartLinkListView(SingleObjectListView):
def get_extra_context(self): def get_extra_context(self):
return { return {
'hide_link': True, 'hide_object': True,
'no_results_icon': icon_smart_link_setup, 'no_results_icon': icon_smart_link_setup,
'no_results_main_link': link_smart_link_create.resolve( 'no_results_main_link': link_smart_link_create.resolve(
context=RequestContext(request=self.request) context=RequestContext(request=self.request)

View File

@@ -53,30 +53,16 @@ class MailerApp(MayanAppConfig):
MailerBackend.initialize() MailerBackend.initialize()
SourceColumn(attribute='datetime', source=LogEntry)
SourceColumn(attribute='message', source=LogEntry)
SourceColumn(attribute='label', is_identifier=True, source=UserMailer)
SourceColumn( SourceColumn(
source=LogEntry, label=_('Date and time'), attribute='datetime' attribute='default', source=UserMailer, widget=TwoStateWidget
) )
SourceColumn( SourceColumn(
source=LogEntry, label=_('Message'), attribute='message' attribute='enabled', source=UserMailer, widget=TwoStateWidget
)
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'
) )
SourceColumn(attribute='backend_label', source=UserMailer)
ModelPermission.register( ModelPermission.register(
model=Document, permissions=( model=Document, permissions=(

View File

@@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
class LogEntry(models.Model): class LogEntry(models.Model):
datetime = models.DateTimeField( 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( message = models.TextField(
blank=True, editable=False, verbose_name=_('Message') blank=True, editable=False, verbose_name=_('Message')
@@ -76,6 +76,7 @@ class UserMailer(models.Model):
property. property.
""" """
return self.get_backend().label return self.get_backend().label
backend_label.short_description = _('Backend label')
def dumps(self, data): def dumps(self, data):
""" """

View File

@@ -35,15 +35,41 @@ def decode_metadata_from_querystring(querystring=None):
return metadata_list 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 Return a printable representation of a metadata list
document
""" """
for item in metadata_list: return ', '.join(metadata_repr_as_list(metadata_list))
save_metadata(
metadata_dict=item, document=document, create=create, _user=_user
) 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): 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) 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)) for item in metadata_list:
save_metadata(
metadata_dict=item, document=document, create=create, _user=_user
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
)

View File

@@ -54,7 +54,7 @@ from .permissions import (
) )
from .queues import * # NOQA from .queues import * # NOQA
from .search import metadata_type_search # 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__) logger = logging.getLogger(__name__)
@@ -144,27 +144,27 @@ class MetadataApp(MayanAppConfig):
) )
SourceColumn( SourceColumn(
source=Document, label=_('Metadata'), func=widget_get_metadata_string, source=Document
func=lambda context: get_metadata_string(context['object'])
) )
SourceColumn( SourceColumn(
source=DocumentPageSearchResult, label=_('Metadata'), func=widget_get_metadata_string, kwargs={'attribute': 'document'},
func=lambda context: get_metadata_string( source=DocumentPageSearchResult,
context['object'].document
)
) )
SourceColumn( SourceColumn(
source=DocumentMetadata, label=_('Value'), attribute='metadata_type', is_identifier=True,
attribute='value' source=DocumentMetadata
)
SourceColumn(attribute='value', source=DocumentMetadata)
SourceColumn(
attribute='is_required', source=DocumentMetadata,
widget=TwoStateWidget
) )
SourceColumn( SourceColumn(
source=DocumentMetadata, label=_('Required'), attribute='label', is_identifier=True, source=MetadataType
func=lambda context: TwoStateWidget(
state=context['object'].is_required
).render()
) )
SourceColumn(attribute='name', source=MetadataType)
app.conf.task_queues.append( app.conf.task_queues.append(
Queue('metadata', Exchange('metadata'), routing_key='metadata'), Queue('metadata', Exchange('metadata'), routing_key='metadata'),

View File

@@ -265,6 +265,7 @@ class DocumentMetadata(models.Model):
return self.metadata_type.get_required_for( return self.metadata_type.get_required_for(
document_type=self.document.document_type document_type=self.document.document_type
) )
is_required.fget.short_description = _('Required')
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.metadata_type.pk not in self.document.document_type.metadata.values_list('metadata_type', flat=True): if self.metadata_type.pk not in self.document.document_type.metadata.values_list('metadata_type', flat=True):

View File

@@ -402,7 +402,7 @@ class DocumentMetadataListView(SingleObjectListView):
def get_extra_context(self): def get_extra_context(self):
document = self.get_document() document = self.get_document()
return { return {
'hide_link': True, 'hide_object': True,
'object': document, 'object': document,
'no_results_icon': icon_metadata, 'no_results_icon': icon_metadata,
'no_results_main_link': link_metadata_add.resolve( 'no_results_main_link': link_metadata_add.resolve(
@@ -618,13 +618,7 @@ class MetadataTypeListView(SingleObjectListView):
def get_extra_context(self): def get_extra_context(self):
return { return {
'extra_columns': ( 'hide_object': True,
{
'name': _('Internal name'),
'attribute': 'name',
},
),
'hide_link': True,
'no_results_icon': icon_metadata, 'no_results_icon': icon_metadata,
'no_results_main_link': link_setup_metadata_type_create.resolve( 'no_results_main_link': link_setup_metadata_type_create.resolve(
context=RequestContext(request=self.request) context=RequestContext(request=self.request)

View File

@@ -1,17 +1,26 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.utils.html import format_html_join 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 Return a formated representation of a document's metadata values
""" """
obj = context['object']
if attribute:
obj = getattr(context['object'], attribute)
return format_html_join( return format_html_join(
'\n', '<div class="metadata-display"><b>{}: </b><span data-metadata-type="{}" data-pk="{}">{}</span></div>', '\n', '<div class="metadata-display"><b>{}: </b><span data-metadata-type="{}" data-pk="{}">{}</span></div>',
( (
( (
document_metadata.metadata_type, document_metadata.metadata_type_id, document_metadata.id, document_metadata.value 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')

View File

@@ -12,6 +12,7 @@ from mayan.apps.acls.permissions import (
from mayan.apps.common import ( from mayan.apps.common import (
MayanAppConfig, menu_list_facet, menu_object, menu_secondary, menu_setup MayanAppConfig, menu_list_facet, menu_object, menu_secondary, menu_setup
) )
from mayan.apps.common.widgets import TwoStateWidget
from mayan.apps.navigation import SourceColumn from mayan.apps.navigation import SourceColumn
from .links import ( from .links import (
@@ -45,16 +46,18 @@ class MOTDApp(MayanAppConfig):
permission_message_view permission_message_view
) )
) )
SourceColumn( SourceColumn(
source=Message, label=_('Enabled'), attribute='enabled' attribute='label', is_identifier=True, source=Message
) )
SourceColumn( SourceColumn(
source=Message, label=_('Start date time'), attribute='enabled', source=Message, widget=TwoStateWidget
func=lambda context: context['object'].start_datetime or _('None')
) )
SourceColumn( SourceColumn(
source=Message, label=_('End date time'), attribute='start_datetime', empty_value=_('None'), source=Message
func=lambda context: context['object'].end_datetime or _('None') )
SourceColumn(
attribute='end_datetime', empty_value=_('None'), source=Message
) )
menu_list_facet.bind_links( menu_list_facet.bind_links(

View File

@@ -65,7 +65,7 @@ class MessageListView(SingleObjectListView):
def get_extra_context(self): def get_extra_context(self):
return { return {
'hide_link': True, 'hide_object': True,
'no_results_icon': icon_message_list, 'no_results_icon': icon_message_list,
'no_results_main_link': link_message_create.resolve( 'no_results_main_link': link_message_create.resolve(
context=RequestContext(request=self.request) context=RequestContext(request=self.request)

View File

@@ -15,10 +15,13 @@ from django.template import VariableDoesNotExist, Variable
from django.template.defaulttags import URLNode from django.template.defaulttags import URLNode
from django.urls import Resolver404, resolve from django.urls import Resolver404, resolve
from django.utils.encoding import force_str, force_text 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.common.utils import resolve_attribute
from mayan.apps.permissions import Permission from mayan.apps.permissions import Permission
from .exceptions import NavigationError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -527,10 +530,11 @@ class SourceColumn(object):
else: else:
return result 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.source = source
self._label = label self._label = label
self.attribute = attribute self.attribute = attribute
self.empty_value = empty_value
self.func = func self.func = func
self.kwargs = kwargs or {} self.kwargs = kwargs or {}
self.order = order or 0 self.order = order or 0
@@ -538,20 +542,29 @@ class SourceColumn(object):
self.__class__._registry.setdefault(source, []) self.__class__._registry.setdefault(source, [])
self.__class__._registry[source].append(self) self.__class__._registry[source].append(self)
self.label = None 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() self._calculate_label()
def _calculate_label(self): def _calculate_label(self):
if not self._label: if not self._label:
if self.attribute: if self.attribute:
name, model = SourceColumn.get_attribute_recursive( try:
attribute=self.attribute, model=self.source._meta.model attribute = resolve_attribute(obj=self.source, attribute=self.attribute)
) self._label = getattr(attribute, 'short_description')
self._label = label_for_field( except AttributeError:
name=name, model=model name, model = SourceColumn.get_attribute_recursive(
) attribute=self.attribute, model=self.source._meta.model
)
self._label = label_for_field(
name=name, model=model
)
else: else:
self._label = 'Function' self._label = getattr(self.func, 'short_description', _('Unnamed function'))
self.label = self._label self.label = self._label
@@ -564,7 +577,17 @@ class SourceColumn(object):
elif self.func: elif self.func:
result = self.func(context=context, **self.kwargs) 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): class Text(Link):

View File

@@ -0,0 +1,5 @@
from __future__ import unicode_literals
class NavigationError(Exception):
"""Base navigation app exception"""

View File

@@ -110,16 +110,16 @@ class OCRApp(MayanAppConfig):
) )
SourceColumn( SourceColumn(
source=DocumentVersionOCRError, label=_('Document'), func=lambda context: document_link(
func=lambda context: document_link(context['object'].document_version.document) context['object'].document_version.document
), label=_('Document'), source=DocumentVersionOCRError
) )
SourceColumn( SourceColumn(
source=DocumentVersionOCRError, label=_('Added'), attribute='datetime_submitted', label=_('Date and time'),
attribute='datetime_submitted' source=DocumentVersionOCRError
) )
SourceColumn( SourceColumn(
source=DocumentVersionOCRError, label=_('Result'), attribute='result', source=DocumentVersionOCRError
attribute='result'
) )
app.conf.task_queues.append( app.conf.task_queues.append(

View File

@@ -11,6 +11,7 @@ from mayan.apps.common import (
menu_secondary, menu_setup menu_secondary, menu_setup
) )
from mayan.apps.common.signals import perform_upgrade from mayan.apps.common.signals import perform_upgrade
from mayan.apps.navigation import SourceColumn
from .handlers import handler_purge_permissions from .handlers import handler_purge_permissions
from .links import ( from .links import (
@@ -48,6 +49,8 @@ class PermissionsApp(MayanAppConfig):
) )
) )
SourceColumn(attribute='label', is_identifier=True, source=Role)
menu_list_facet.bind_links( menu_list_facet.bind_links(
links=( links=(
link_acl_list, link_role_groups, link_role_permissions, link_acl_list, link_role_groups, link_role_permissions,

View File

@@ -199,7 +199,7 @@ class RoleListView(SingleObjectListView):
def get_extra_context(self): def get_extra_context(self):
return { return {
'hide_link': True, 'hide_object': True,
'no_results_icon': icon_role_list, 'no_results_icon': icon_role_list,
'no_results_main_link': link_role_create.resolve( 'no_results_main_link': link_role_create.resolve(
context=RequestContext(request=self.request) context=RequestContext(request=self.request)

View File

@@ -37,4 +37,3 @@ class ClassesTestCase(BaseTestCase):
setting_value = setting_paginate_by.value setting_value = setting_paginate_by.value
os.environ.pop('MAYAN_{}'.format(TEST_SETTING_NAME)) os.environ.pop('MAYAN_{}'.format(TEST_SETTING_NAME))
self.assertEqual(setting_value, TEST_SETTING_VALUE) self.assertEqual(setting_value, TEST_SETTING_VALUE)

View File

@@ -101,7 +101,7 @@ class TagsApp(MayanAppConfig):
) )
SourceColumn( SourceColumn(
attribute='label', source=DocumentTag, attribute='label', is_identifier=True, source=DocumentTag,
) )
SourceColumn( SourceColumn(
attribute='get_preview_widget', source=DocumentTag attribute='get_preview_widget', source=DocumentTag

View File

@@ -33,16 +33,12 @@ class TaskManagerApp(MayanAppConfig):
source=CeleryQueue, label=_('Name'), attribute='name' source=CeleryQueue, label=_('Name'), attribute='name'
) )
SourceColumn( SourceColumn(
source=CeleryQueue, label=_('Default queue?'), attribute='is_default_queue', label=_('Default queue?'),
func=lambda context: TwoStateWidget( source=CeleryQueue, widget=TwoStateWidget
state=context['object'].is_default_queue
).render()
) )
SourceColumn( SourceColumn(
source=CeleryQueue, label=_('Is transient?'), attribute='is_transient', label=_('Is transient?'),
func=lambda context: TwoStateWidget( source=CeleryQueue, widget=TwoStateWidget
state=context['object'].is_transient
).render()
) )
SourceColumn( SourceColumn(
source=Task, label=_('Type'), attribute='task_type' source=Task, label=_('Type'), attribute='task_type'
@@ -54,12 +50,12 @@ class TaskManagerApp(MayanAppConfig):
source=Task, label=_('Host'), source=Task, label=_('Host'),
func=lambda context: context['object'].kwargs['hostname'] func=lambda context: context['object'].kwargs['hostname']
) )
SourceColumn( #SourceColumn(
source=Task, label=_('Acknowledged'), # source=Task, label=_('Acknowledged'),
func=lambda context: TwoStateWidget( # func=lambda context: TwoStateWidget(
state=context['object'].kwargs['acknowledged'] # state=context['object'].kwargs['acknowledged']
).render() # ).render()
) #)
SourceColumn( SourceColumn(
source=Task, label=_('Arguments'), source=Task, label=_('Arguments'),
func=lambda context: context['object'].kwargs['args'] func=lambda context: context['object'].kwargs['args']

View File

@@ -31,20 +31,7 @@ from .permissions import (
permission_user_view permission_user_view
) )
from .search import * # NOQA from .search import * # NOQA
from .utils import get_groups, get_users
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()
]
)
class UserManagementApp(MayanAppConfig): class UserManagementApp(MayanAppConfig):
@@ -89,27 +76,27 @@ class UserManagementApp(MayanAppConfig):
permission_user_view permission_user_view
) )
) )
SourceColumn(attribute='name', is_identifier=True, source=Group)
SourceColumn( 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( 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( SourceColumn(
source=User, label=_('Email'), attribute='email' attribute='has_usable_password', source=User, widget=TwoStateWidget
) )
SourceColumn( SourceColumn(
source=User, label=_('Active'), attribute='has_usable_password', label=_('Has usable password?'),
func=lambda context: TwoStateWidget( source=User, widget=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()
) )
menu_list_facet.bind_links( menu_list_facet.bind_links(

View File

@@ -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()
]
)

View File

@@ -55,7 +55,7 @@ class GroupListView(SingleObjectListView):
def get_extra_context(self): def get_extra_context(self):
return { return {
'hide_link': True, 'hide_object': True,
'no_results_icon': icon_group_setup, 'no_results_icon': icon_group_setup,
'no_results_main_link': link_group_create.resolve( 'no_results_main_link': link_group_create.resolve(
context=RequestContext(request=self.request) context=RequestContext(request=self.request)
@@ -261,7 +261,7 @@ class UserListView(SingleObjectListView):
def get_extra_context(self): def get_extra_context(self):
return { return {
'hide_link': True, 'hide_object': True,
'no_results_icon': icon_user_setup, 'no_results_icon': icon_user_setup,
'no_results_main_link': link_user_create.resolve( 'no_results_main_link': link_user_create.resolve(
context=RequestContext(request=self.request) context=RequestContext(request=self.request)