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:
@@ -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)
|
||||
==================
|
||||
|
||||
@@ -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 %}
|
||||
</span>
|
||||
</label>
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
{% endif %}
|
||||
{% if not hide_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 %}
|
||||
{% endif %}
|
||||
{% for column in extra_columns %}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
'<div class="{} text-success">{}</div>'.format(
|
||||
center_class, self.icon_ok.render()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.template import Context, Library
|
||||
from django.template import Library
|
||||
|
||||
from ..classes import Dashboard
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
@@ -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,))
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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',),
|
||||
),
|
||||
]
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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,)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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 '
|
||||
|
||||
@@ -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('<a href="%s">%s</a>' % (
|
||||
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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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(
|
||||
'<a href="%(url)s">%(label)s</a>' % {
|
||||
'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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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=(
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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', '<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
|
||||
) for document_metadata in document.metadata.all()
|
||||
) for document_metadata in obj.metadata.all()
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
widget_get_metadata_string.short_description = _('Metadata')
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
5
mayan/apps/navigation/exceptions.py
Normal file
5
mayan/apps/navigation/exceptions.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
class NavigationError(Exception):
|
||||
"""Base navigation app exception"""
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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(
|
||||
|
||||
18
mayan/apps/user_management/utils.py
Normal file
18
mayan/apps/user_management/utils.py
Normal 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()
|
||||
]
|
||||
)
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user