diff --git a/mayan/apps/acls/views.py b/mayan/apps/acls/views.py index 2d61c8d654..54c928b80c 100644 --- a/mayan/apps/acls/views.py +++ b/mayan/apps/acls/views.py @@ -10,7 +10,6 @@ from django.http import Http404 from django.shortcuts import get_object_or_404 from django.utils.translation import ugettext_lazy as _ -from common.utils import encapsulate from common.views import ( AssignRemoveView, SingleObjectCreateView, SingleObjectDeleteView, SingleObjectListView diff --git a/mayan/apps/appearance/templates/appearance/generic_list_subtemplate.html b/mayan/apps/appearance/templates/appearance/generic_list_subtemplate.html index 6c01f9fe11..cb1577b986 100644 --- a/mayan/apps/appearance/templates/appearance/generic_list_subtemplate.html +++ b/mayan/apps/appearance/templates/appearance/generic_list_subtemplate.html @@ -87,7 +87,7 @@ {% endif %} {% if not hide_columns %} {% for column in object|get_source_columns %} - {{ object|object_property:column.attribute }} + {% source_column_resolve column=column %} {% endfor %} {% endif %} {% for column in extra_columns %} diff --git a/mayan/apps/converter/apps.py b/mayan/apps/converter/apps.py index 6ae2a5ebc9..66f776cd78 100644 --- a/mayan/apps/converter/apps.py +++ b/mayan/apps/converter/apps.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals from django.utils.translation import ugettext_lazy as _ from common import MayanAppConfig, menu_object, menu_sidebar -from common.utils import encapsulate from navigation import SourceColumn from .links import ( @@ -23,9 +22,7 @@ class ConverterApp(MayanAppConfig): SourceColumn(source=Transformation, label=_('Order'), attribute='order') SourceColumn( source=Transformation, label=_('Transformation'), - attribute=encapsulate( - lambda transformation: unicode(transformation) - ) + func=lambda context: unicode(context['object']) ) SourceColumn( source=Transformation, label=_('Arguments'), attribute='arguments' diff --git a/mayan/apps/django_gpg/apps.py b/mayan/apps/django_gpg/apps.py index 7a228f47a0..b01645acb4 100644 --- a/mayan/apps/django_gpg/apps.py +++ b/mayan/apps/django_gpg/apps.py @@ -5,7 +5,6 @@ from datetime import datetime from django.utils.translation import ugettext_lazy as _ from common import MayanAppConfig, menu_object, menu_setup, menu_sidebar -from common.utils import encapsulate from navigation import SourceColumn from .api import Key, KeyStub @@ -23,34 +22,31 @@ class DjangoGPGApp(MayanAppConfig): def ready(self): super(DjangoGPGApp, self).ready() - SourceColumn(source=Key, label='ID', attribute='key_id') + SourceColumn(source=Key, label=_('ID'), attribute='key_id') SourceColumn( - source=Key, label='Owner', attribute=encapsulate( - lambda key: ', '.join(key.uids) - ) + source=Key, label=_('Owner'), + func=lambda context: ', '.join(context['object'].uids) ) SourceColumn( - source=KeyStub, label='ID', attribute=encapsulate( - lambda key: '...{0}'.format(key.key_id[-16:]) - ) + source=KeyStub, label=_('ID'), + func=lambda context: '...{0}'.format(context['object'].key_id[-16:]) ) - SourceColumn(source=KeyStub, label='Type', attribute='key_type') + SourceColumn(source=KeyStub, label=_('Type'), attribute='key_type') SourceColumn( - source=KeyStub, label='Creation date', attribute=encapsulate( - lambda key: datetime.fromtimestamp(int(key.date)) + source=KeyStub, label=_('Creation date'), + func=lambda context: datetime.fromtimestamp( + int(context['object'].date) ) ) SourceColumn( - source=KeyStub, label='Expiration date', attribute=encapsulate( - lambda key: datetime.fromtimestamp(int(key.expires)) if key.expires else _('No expiration') - ) + source=KeyStub, label=_('Expiration date'), + func=lambda context: datetime.fromtimestamp(int(context['object'].expires)) if context['object'].expires else _('No expiration') ) - SourceColumn(source=KeyStub, label='Length', attribute='length') + SourceColumn(source=KeyStub, label=_('Length'), attribute='length') SourceColumn( - source=KeyStub, label='Identities', attribute=encapsulate( - lambda key: ', '.join(key.uids) - ) + source=KeyStub, label=_('Identities'), + func=lambda context: ', '.join(context['object'].uids) ) menu_object.bind_links(links=(link_key_delete,), sources=(Key,)) diff --git a/mayan/apps/django_gpg/views.py b/mayan/apps/django_gpg/views.py index 4ecd1bb673..ce3ec951c3 100644 --- a/mayan/apps/django_gpg/views.py +++ b/mayan/apps/django_gpg/views.py @@ -12,7 +12,6 @@ from django.template import RequestContext from django.utils.translation import ugettext_lazy as _ from common.generics import SimpleView, SingleObjectListView -from common.utils import encapsulate from permissions import Permission from .api import Key diff --git a/mayan/apps/document_comments/apps.py b/mayan/apps/document_comments/apps.py index 1c82786a64..378ed7f66e 100644 --- a/mayan/apps/document_comments/apps.py +++ b/mayan/apps/document_comments/apps.py @@ -4,7 +4,6 @@ from django.utils.translation import ugettext_lazy as _ from acls import ModelPermission from common import MayanAppConfig, menu_facet, menu_object, menu_sidebar -from common.utils import encapsulate from documents.models import Document from navigation import SourceColumn @@ -37,9 +36,7 @@ class DocumentCommentsApp(MayanAppConfig): SourceColumn(source=Comment, label=_('Date'), attribute='submit_date') SourceColumn( source=Comment, label=_('User'), - attribute=encapsulate( - lambda x: x.user.get_full_name() if x.user.get_full_name() else x.user - ) + func=lambda context: context['object'].user.get_full_name() if context['object'].user.get_full_name() else context['object'].user ) SourceColumn(source=Comment, label=_('Comment'), attribute='comment') diff --git a/mayan/apps/document_indexing/apps.py b/mayan/apps/document_indexing/apps.py index 14d229629e..02a7ef4993 100644 --- a/mayan/apps/document_indexing/apps.py +++ b/mayan/apps/document_indexing/apps.py @@ -9,10 +9,12 @@ from common import ( MayanAppConfig, menu_facet, menu_main, menu_object, menu_secondary, menu_setup, menu_tools ) +from common.widgets import two_state_template from documents.models import Document from documents.signals import post_document_created from mayan.celery import app from metadata.models import DocumentMetadata +from navigation import SourceColumn from rest_api.classes import APIEndPoint from .handlers import ( @@ -27,7 +29,11 @@ from .links import ( link_template_node_create, link_template_node_delete, link_template_node_edit ) -from .models import Index, IndexTemplateNode +from .models import ( + DocumentIndexInstanceNode, Index, IndexInstance, IndexInstanceNode, + IndexTemplateNode +) +from .widgets import get_breadcrumbs, index_instance_item_link, node_level class DocumentIndexingApp(MayanAppConfig): @@ -41,6 +47,60 @@ class DocumentIndexingApp(MayanAppConfig): APIEndPoint(app=self, version_string='1') + SourceColumn(source=Index, label=_('Label'), attribute='label') + SourceColumn(source=Index, label=_('Slug'), attribute='slug') + SourceColumn( + source=Index, label=_('Enabled'), + func=lambda context: two_state_template(context['object'].enabled) + ) + + SourceColumn( + source=IndexInstance, label=_('Items'), + func=lambda context: context['object'].get_items_count( + user=context['request'].user + ) + ) + SourceColumn( + source=IndexInstance, label=_('Document types'), attribute='get_document_types_names' + ) + + SourceColumn( + source=IndexTemplateNode, label=_('Level'), + func=lambda context: node_level(context['object']) + ) + SourceColumn( + source=IndexTemplateNode, label=_('Enabled'), + func=lambda context: two_state_template(context['object'].enabled) + ) + SourceColumn( + source=IndexTemplateNode, label=_('Has document links?'), + func=lambda context: two_state_template(context['object'].link_documents) + ) + + SourceColumn( + source=IndexInstanceNode, label=_('Node'), + func=lambda context: index_instance_item_link(context['object']) + ) + SourceColumn( + source=IndexInstanceNode, label=_('Items'), + func=lambda context: context['object'].get_item_count( + user=context['request'].user + ) + ) + + SourceColumn( + source=DocumentIndexInstanceNode, label=_('Node'), + func=lambda context: get_breadcrumbs( + index_instance_node=context['object'], single_link=True, + ) + ) + SourceColumn( + source=DocumentIndexInstanceNode, label=_('Items'), + func=lambda context: context['object'].get_item_count( + user=context['request'].user + ) + ) + app.conf.CELERY_QUEUES.append( Queue('indexing', Exchange('indexing'), routing_key='indexing'), ) diff --git a/mayan/apps/document_indexing/models.py b/mayan/apps/document_indexing/models.py index e9c3caa870..af276c14c4 100644 --- a/mayan/apps/document_indexing/models.py +++ b/mayan/apps/document_indexing/models.py @@ -1,5 +1,6 @@ -from __future__ import unicode_literals +from __future__ import absolute_import, unicode_literals +from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse from django.db import models from django.utils.encoding import python_2_unicode_compatible @@ -8,7 +9,10 @@ from django.utils.translation import ugettext, ugettext_lazy as _ from mptt.fields import TreeForeignKey from mptt.models import MPTTModel +from acls.models import AccessControlList from documents.models import Document, DocumentType +from documents.permissions import permission_document_view +from permissions import Permission from .managers import IndexManager, IndexInstanceNodeManager @@ -58,7 +62,10 @@ class Index(models.Model): return '#' def save(self, *args, **kwargs): - """Automatically create the root index template node""" + """ + Automatically create the root index template node + """ + super(Index, self).save(*args, **kwargs) IndexTemplateNode.objects.get_or_create(parent=None, index=self) @@ -67,15 +74,28 @@ class Index(models.Model): [unicode(document_type) for document_type in self.document_types.all()] or ['None'] ) + class Meta: + verbose_name = _('Index') + verbose_name_plural = _('Indexes') + + +class IndexInstance(Index): def get_instance_node_count(self): try: return self.instance_root.get_descendant_count() except IndexInstanceNode.DoesNotExist: return 0 + def get_items_count(self, user): + try: + return self.instance_root.get_item_count(user=user) + except IndexInstanceNode.DoesNotExist: + return 0 + class Meta: - verbose_name = _('Index') - verbose_name_plural = _('Indexes') + proxy = True + verbose_name = _('Index instance') + verbose_name_plural = _('Index instances') @python_2_unicode_compatible @@ -140,9 +160,6 @@ class IndexInstanceNode(MPTTModel): def __str__(self): return self.value - def index(self): - return self.index_template_node.index - def get_absolute_url(self): return reverse('indexing:index_instance_node_view', args=[self.pk]) @@ -151,6 +168,38 @@ class IndexInstanceNode(MPTTModel): # Convenience method for serializer return self.get_children() + def index(self): + return self.index_template_node.index + + def get_item_count(self, user): + if self.index_template_node.link_documents: + queryset = self.documents + + try: + Permission.check_permissions(user, (permission_document_view,)) + except PermissionDenied: + queryset = AccessControlList.objects.filter_by_access( + permission_document_view, user, queryset + ) + + return queryset.count() + else: + return self.get_children().count() + class Meta: verbose_name = _('Index node instance') verbose_name_plural = _('Indexes node instances') + + +class DocumentIndexInstanceNodeManager(models.Manager): + def get_for(self, document): + return self.filter(documents=document) + + +class DocumentIndexInstanceNode(IndexInstanceNode): + objects = DocumentIndexInstanceNodeManager() + + class Meta: + proxy = True + verbose_name = _('Document index node instance') + verbose_name_plural = _('Document indexes node instances') diff --git a/mayan/apps/document_indexing/views.py b/mayan/apps/document_indexing/views.py index 5ee33dff18..e36cf9bf24 100644 --- a/mayan/apps/document_indexing/views.py +++ b/mayan/apps/document_indexing/views.py @@ -11,7 +11,6 @@ from django.utils.html import mark_safe from django.utils.translation import ugettext_lazy as _ from acls.models import AccessControlList -from common.utils import encapsulate from common.views import ( AssignRemoveView, SingleObjectCreateView, SingleObjectDeleteView, SingleObjectEditView, SingleObjectListView @@ -23,7 +22,10 @@ from documents.views import DocumentListView from permissions import Permission from .forms import IndexTemplateNodeForm -from .models import Index, IndexInstanceNode, IndexTemplateNode +from .models import ( + DocumentIndexInstanceNode, Index, IndexInstance, IndexInstanceNode, + IndexTemplateNode +) from .permissions import ( permission_document_indexing_create, permission_document_indexing_delete, permission_document_indexing_edit, @@ -49,17 +51,8 @@ class SetupIndexListView(SingleObjectListView): def get_extra_context(self): return { - 'title': _('Indexes'), 'hide_object': True, - 'extra_columns': [ - {'name': _('Label'), 'attribute': 'label'}, - {'name': _('Slug'), 'attribute': 'slug'}, - { - 'name': _('Enabled'), 'attribute': encapsulate( - lambda x: two_state_template(x.enabled) - ) - }, - ] + 'title': _('Indexes'), } @@ -71,8 +64,8 @@ class SetupIndexEditView(SingleObjectEditView): def get_extra_context(self): return { - 'title': _('Edit index: %s') % self.get_object(), 'object': self.get_object(), + 'title': _('Edit index: %s') % self.get_object(), } @@ -83,8 +76,8 @@ class SetupIndexDeleteView(SingleObjectDeleteView): def get_extra_context(self): return { - 'title': _('Delete the index: %s?') % self.get_object(), 'object': self.get_object(), + 'title': _('Delete the index: %s?') % self.get_object(), } @@ -99,23 +92,6 @@ class SetupIndexTreeTemplateListView(SingleObjectListView): def get_extra_context(self): return { - 'extra_columns': ( - { - 'name': _('Level'), 'attribute': encapsulate( - lambda node: node_level(node) - ) - }, - { - 'name': _('Enabled'), 'attribute': encapsulate( - lambda node: two_state_template(node.enabled) - ) - }, - { - 'name': _('Has document links?'), 'attribute': encapsulate( - lambda node: two_state_template(node.link_documents) - ) - }, - ), 'hide_object': True, 'index': self.get_index(), 'navigation_object_list': ('index',), @@ -276,54 +252,17 @@ def template_node_delete(request, node_pk): class IndexListView(SingleObjectListView): - @staticmethod - def get_items_count(instance): - try: - if instance.template_root.link_documents: - return instance.instance_root.documents.count() - else: - return instance.instance_root.get_children().count() - except IndexInstanceNode.DoesNotExist: - return 0 - - queryset = Index.objects.filter(enabled=True) object_permission = permission_document_indexing_view + queryset = IndexInstance.objects.filter(enabled=True) def get_extra_context(self): return { - 'title': _('Indexes'), 'hide_links': True, - 'extra_columns': [ - { - 'name': _('Items'), 'attribute': encapsulate( - lambda instance: IndexListView.get_items_count(instance) - ) - }, - { - 'name': _('Document types'), - 'attribute': 'get_document_types_names' - }, - ], + 'title': _('Indexes'), } class IndexInstanceNodeView(DocumentListView, SingleObjectListView): - @staticmethod - def get_item_count(instance, user): - if instance.index_template_node.link_documents: - queryset = instance.documents - - try: - Permission.check_permissions(user, (permission_document_view,)) - except PermissionDenied: - queryset = AccessControlList.objects.filter_by_access( - permission_document_view, user, queryset - ) - - return queryset.count() - else: - return instance.get_children().count() - def dispatch(self, request, *args, **kwargs): self.index_instance = get_object_or_404( IndexInstanceNode, pk=self.kwargs['pk'] @@ -371,25 +310,7 @@ class IndexInstanceNodeView(DocumentListView, SingleObjectListView): } if self.index_instance and not self.index_instance.index_template_node.link_documents: - context.update( - { - 'extra_columns': [ - { - 'name': _('Node'), - 'attribute': encapsulate( - lambda x: index_instance_item_link(x) - ) - }, - { - 'name': _('Items'), - 'attribute': encapsulate( - lambda instance: IndexInstanceNodeView.get_item_count(instance=instance, user=self.request.user) - ) - } - ], - 'hide_object': True, - } - ) + context.update({'hide_object': True}) return context @@ -446,16 +367,6 @@ class DocumentIndexNodeListView(SingleObjectListView): def get_extra_context(self): return { - 'extra_columns': ( - { - 'name': _('Node'), - 'attribute': encapsulate( - lambda node: get_breadcrumbs( - index_instance_node=node, single_link=True, include_count=True - ) - ) - }, - ), 'hide_object': True, 'object': self.get_document(), 'title': _( @@ -464,4 +375,4 @@ class DocumentIndexNodeListView(SingleObjectListView): } def get_queryset(self): - return self.get_document().node_instances.all() + return DocumentIndexInstanceNode.objects.get_for(self.get_document()) diff --git a/mayan/apps/document_indexing/widgets.py b/mayan/apps/document_indexing/widgets.py index 6629af0be6..aafaa2fc50 100644 --- a/mayan/apps/document_indexing/widgets.py +++ b/mayan/apps/document_indexing/widgets.py @@ -7,22 +7,6 @@ from django.utils.translation import ugettext from .models import IndexInstanceNode -def index_instance_item_link(index_instance_item): - if isinstance(index_instance_item, IndexInstanceNode): - if index_instance_item.index_template_node.link_documents: - icon_template = '' - else: - icon_template = '' - else: - icon_template = '' - - return mark_safe('%(icon_template)s %(text)s' % { - 'url': index_instance_item.get_absolute_url(), - 'icon_template': icon_template, - 'text': index_instance_item - }) - - def get_instance_link(index_instance_node, text=None, simple=False): """ Return an HTML anchor to an index instance @@ -75,6 +59,22 @@ def get_breadcrumbs(index_instance_node, simple=False, single_link=False, includ return mark_safe(' '.join(output)) +def index_instance_item_link(index_instance_item): + if isinstance(index_instance_item, IndexInstanceNode): + if index_instance_item.index_template_node.link_documents: + icon_template = '' + else: + icon_template = '' + else: + icon_template = '' + + return mark_safe('%(icon_template)s %(text)s' % { + 'url': index_instance_item.get_absolute_url(), + 'icon_template': icon_template, + 'text': index_instance_item + }) + + def node_level(node): """ Render an indented tree like output for a specific node diff --git a/mayan/apps/document_states/apps.py b/mayan/apps/document_states/apps.py index 4ec4b24a83..1daee7de10 100644 --- a/mayan/apps/document_states/apps.py +++ b/mayan/apps/document_states/apps.py @@ -7,7 +7,6 @@ from common import ( MayanAppConfig, menu_facet, menu_object, menu_secondary, menu_setup, menu_sidebar ) -from common.utils import encapsulate from common.widgets import two_state_template from documents.models import Document from navigation import SourceColumn @@ -39,9 +38,7 @@ class DocumentStatesApp(MayanAppConfig): SourceColumn( source=Workflow, label=_('Initial state'), - attribute=encapsulate( - lambda workflow: workflow.get_initial_state() or _('None') - ) + func=lambda context: context['object'].get_initial_state() or _('None') ) SourceColumn( @@ -50,10 +47,8 @@ class DocumentStatesApp(MayanAppConfig): ) SourceColumn( source=WorkflowInstance, label=_('User'), - attribute=encapsulate( - lambda workflow: getattr( - workflow.get_last_log_entry(), 'user', _('None') - ) + func=lambda context: getattr( + context['object'].get_last_log_entry(), 'user', _('None') ) ) SourceColumn( @@ -62,16 +57,14 @@ class DocumentStatesApp(MayanAppConfig): ) SourceColumn( source=WorkflowInstance, label=_('Date and time'), - attribute=encapsulate( - lambda workflow: getattr( - workflow.get_last_log_entry(), 'datetime', _('None') - ) + func=lambda context: getattr( + context['object'].get_last_log_entry(), 'datetime', _('None') ) ) SourceColumn( source=WorkflowInstance, label=_('Completion'), - attribute=encapsulate(lambda workflow: getattr( - workflow.get_current_state(), 'completion', _('None')) + func=lambda context: getattr( + context['object'].get_current_state(), 'completion', _('None') ) ) @@ -93,9 +86,7 @@ class DocumentStatesApp(MayanAppConfig): SourceColumn( source=WorkflowState, label=_('Is initial state?'), - attribute=encapsulate( - lambda state: two_state_template(state.initial) - ) + func=lambda context: two_state_template(context['object'].initial) ) SourceColumn( source=WorkflowState, label=_('Completion'), attribute='completion' diff --git a/mayan/apps/documents/apps.py b/mayan/apps/documents/apps.py index e0e19146ea..e2d077bbbd 100644 --- a/mayan/apps/documents/apps.py +++ b/mayan/apps/documents/apps.py @@ -17,7 +17,7 @@ from common import ( ) from common.classes import ModelAttribute from common.signals import post_initial_setup -from common.utils import encapsulate +from common.widgets import two_state_template from converter.links import link_transformation_list from converter.permissions import ( permission_transformation_create, @@ -125,17 +125,27 @@ class DocumentsApp(MayanAppConfig): SourceColumn( source=Document, label=_('Thumbnail'), - attribute=encapsulate( - lambda document: document_thumbnail( - document, gallery_name='documents:document_list', - size=setting_thumbnail_size.value, - title=getattr(document, 'label', None), - ) + func=lambda context: document_thumbnail( + context['object'], gallery_name='documents:document_list', + size=setting_thumbnail_size.value, + title=getattr(context['object'], 'label', None), ) ) SourceColumn( source=Document, label=_('Type'), attribute='document_type' ) + + # TODO: make permission aware + SourceColumn( + source=DocumentType, label=_('Documents'), + func=lambda context: context['object'].documents.count() + ) + + SourceColumn( + source=DocumentTypeFilename, label=_('Enabled'), + func=lambda context: two_state_template(context['object'].enabled) + ) + SourceColumn( source=DeletedDocument, label=_('Type'), attribute='document_type' ) @@ -144,6 +154,23 @@ class DocumentsApp(MayanAppConfig): attribute='deleted_date_time' ) + SourceColumn( + source=DocumentVersion, label=_('Time and date'), + attribute='timestamp' + ) + SourceColumn( + source=DocumentVersion, label=_('MIME type'), + attribute='mimetype' + ) + SourceColumn( + source=DocumentVersion, label=_('Encoding'), + attribute='encoding' + ) + SourceColumn( + source=DocumentVersion, label=_('Comment'), + attribute='comment' + ) + app.conf.CELERYBEAT_SCHEDULE.update( { 'task_check_delete_periods': { diff --git a/mayan/apps/documents/views.py b/mayan/apps/documents/views.py index 8b4daa76d7..5afa5777f9 100644 --- a/mayan/apps/documents/views.py +++ b/mayan/apps/documents/views.py @@ -954,9 +954,6 @@ class DocumentTypeListView(SingleObjectListView): def get_extra_context(self): return { - 'extra_columns': [ - {'name': _('Documents'), 'attribute': encapsulate(lambda document_type: document_type.documents.count())} - ], 'hide_link': True, 'title': _('Document types'), } @@ -1010,12 +1007,6 @@ class DocumentTypeFilenameListView(SingleObjectListView): def get_extra_context(self): return { 'document_type': self.get_document_type(), - 'extra_columns': ( - { - 'name': _('Enabled'), - 'attribute': encapsulate(lambda filename: two_state_template(filename.enabled)), - }, - ), 'hide_link': True, 'navigation_object_list': ('document_type',), 'title': _('Filenames for document type: %s') % self.get_document_type(), @@ -1148,12 +1139,7 @@ class DocumentVersionListView(SingleObjectListView): def get_extra_context(self): return { - 'extra_columns': ( - {'name': _('Time and date'), 'attribute': 'timestamp'}, - {'name': _('MIME type'), 'attribute': 'mimetype'}, - {'name': _('Encoding'), 'attribute': 'encoding'}, - {'name': _('Comment'), 'attribute': 'comment'}, - ), 'hide_object': True, 'object': self.get_document(), + 'hide_object': True, 'object': self.get_document(), 'title': _('Versions of document: %s') % self.get_document(), } diff --git a/mayan/apps/events/apps.py b/mayan/apps/events/apps.py index b527aaf87b..28637755c0 100644 --- a/mayan/apps/events/apps.py +++ b/mayan/apps/events/apps.py @@ -5,7 +5,6 @@ from django.utils.translation import ugettext_lazy as _ from actstream.models import Action from common import MayanAppConfig, menu_tools -from common.utils import encapsulate from navigation import SourceColumn from .links import link_events_list @@ -21,8 +20,8 @@ class EventsApp(MayanAppConfig): SourceColumn(source=Action, label=_('Timestamp'), attribute='timestamp') SourceColumn(source=Action, label=_('Actor'), attribute='actor') - SourceColumn(source=Action, label=_('Verb'), attribute=encapsulate( - lambda entry: event_type_link(entry)) + SourceColumn(source=Action, label=_('Verb'), + func=lambda context: event_type_link(context['object']) ) menu_tools.bind_links(links=[link_events_list]) diff --git a/mayan/apps/events/views.py b/mayan/apps/events/views.py index 86574fa494..201e39e203 100644 --- a/mayan/apps/events/views.py +++ b/mayan/apps/events/views.py @@ -26,14 +26,14 @@ class EventListView(SingleObjectListView): def get_extra_context(self): return { - 'extra_columns': [ + 'extra_columns': ( { 'name': _('Target'), 'attribute': encapsulate( lambda entry: event_object_link(entry) ) - } - ], + }, + ), 'hide_object': True, 'title': _('Events'), } @@ -85,14 +85,14 @@ class VerbEventListView(SingleObjectListView): def get_extra_context(self): return { - 'extra_columns': [ + 'extra_columns': ( { 'name': _('Target'), 'attribute': encapsulate( lambda entry: event_object_link(entry) ) - } - ], + }, + ), 'hide_object': True, 'title': _( 'Events of type: %s' diff --git a/mayan/apps/folders/apps.py b/mayan/apps/folders/apps.py index 6c74bd2824..b21f32b534 100644 --- a/mayan/apps/folders/apps.py +++ b/mayan/apps/folders/apps.py @@ -56,6 +56,12 @@ class FoldersApp(MayanAppConfig): source=Folder, label=_('Created'), attribute='datetime_created' ) SourceColumn(source=Folder, label=_('User'), attribute='user') + SourceColumn( + source=Folder, label=_('Documents'), + func=lambda context: context['object'].get_document_count( + user=context['request'].user + ) + ) menu_facet.bind_links( links=(link_document_folder_list,), sources=(Document,) diff --git a/mayan/apps/folders/models.py b/mayan/apps/folders/models.py index a2127c573a..007313cfad 100644 --- a/mayan/apps/folders/models.py +++ b/mayan/apps/folders/models.py @@ -1,12 +1,16 @@ -from __future__ import unicode_literals +from __future__ import absolute_import, unicode_literals from django.contrib.auth.models import User +from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse from django.db import models from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ +from acls.models import AccessControlList from documents.models import Document +from documents.permissions import permission_document_view +from permissions import Permission @python_2_unicode_compatible @@ -28,6 +32,18 @@ class Folder(models.Model): def get_absolute_url(self): return reverse('folders:folder_view', args=[self.pk]) + def get_document_count(self, user): + queryset = self.documents + + try: + Permission.check_permissions(user, (permission_document_view,)) + except PermissionDenied: + queryset = AccessControlList.objects.filter_by_access( + permission_document_view, user, queryset + ) + + return queryset.count() + class Meta: ordering = ('label',) unique_together = ('label', 'user') diff --git a/mayan/apps/folders/views.py b/mayan/apps/folders/views.py index 248d75f9e7..956e38e84c 100644 --- a/mayan/apps/folders/views.py +++ b/mayan/apps/folders/views.py @@ -12,7 +12,6 @@ from django.template import RequestContext from django.utils.translation import ugettext_lazy as _, ungettext from acls.models import AccessControlList -from common.utils import encapsulate from common.views import ( SingleObjectCreateView, SingleObjectEditView, SingleObjectListView ) @@ -48,31 +47,8 @@ class FolderEditView(SingleObjectEditView): class FolderListView(SingleObjectListView): object_permission = permission_folder_view - @staticmethod - def get_document_count(instance, user): - queryset = instance.documents - - try: - Permission.check_permissions(user, (permission_document_view,)) - except PermissionDenied: - queryset = AccessControlList.objects.filter_by_access( - permission_document_view, user, queryset - ) - - return queryset.count() - def get_extra_context(self): return { - 'extra_columns': [ - { - 'name': _('Documents'), - 'attribute': encapsulate( - lambda instance: FolderListView.get_document_count( - instance=instance, user=self.request.user - ) - ) - }, - ], 'hide_link': True, 'title': _('Folders'), } @@ -253,16 +229,6 @@ class DocumentFolderListView(FolderListView): def get_extra_context(self): return { - 'extra_columns': [ - { - 'name': _('Documents'), - 'attribute': encapsulate( - lambda instance: FolderListView.get_document_count( - instance=instance, user=self.request.user - ) - ) - }, - ], 'hide_link': True, 'object': self.document, 'title': _('Folders containing document: %s') % self.document, diff --git a/mayan/apps/installation/apps.py b/mayan/apps/installation/apps.py index 1082417e2a..a5ae30bf39 100644 --- a/mayan/apps/installation/apps.py +++ b/mayan/apps/installation/apps.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals from django.utils.translation import ugettext_lazy as _ from common import MayanAppConfig, menu_tools, menu_object, menu_secondary -from common.utils import encapsulate from navigation import SourceColumn from .classes import Property, PropertyNamespace, PIPNotFound, VirtualEnv @@ -24,7 +23,7 @@ class InstallationApp(MayanAppConfig): ) SourceColumn( source=PropertyNamespace, label=_('Items'), - attribute=encapsulate(lambda entry: len(entry.get_properties())) + func=lambda context: len(context['object'].get_properties()) ) SourceColumn(source=Property, label=_('Label'), attribute='label') diff --git a/mayan/apps/linking/apps.py b/mayan/apps/linking/apps.py index 4b4a8f3773..7643a1e52d 100644 --- a/mayan/apps/linking/apps.py +++ b/mayan/apps/linking/apps.py @@ -9,7 +9,9 @@ from common import ( MayanAppConfig, menu_facet, menu_object, menu_secondary, menu_setup, menu_sidebar ) +from common.widgets import two_state_template from documents.models import Document +from navigation import SourceColumn from .links import ( link_smart_link_create, link_smart_link_condition_create, @@ -41,6 +43,26 @@ class LinkingApp(MayanAppConfig): ) ) + SourceColumn( + source=ResolvedSmartLink, label=_('Label'), + func=lambda context: context['object'].get_dynamic_label( + context['resolved_object'] + ) + ) + + SourceColumn( + source=SmartLink, label=_('Dynamic label'), attribute='dynamic_label' + ) + SourceColumn( + source=SmartLink, label=_('Enabled'), + func=lambda context: two_state_template(context['object'].enabled) + ) + + SourceColumn( + source=SmartLinkCondition, label=_('Enabled'), + func=lambda context: two_state_template(context['object'].enabled) + ) + menu_facet.bind_links( links=(link_smart_link_instances_for_document,), sources=(Document,) diff --git a/mayan/apps/linking/views.py b/mayan/apps/linking/views.py index fb6f21773a..171961bd54 100644 --- a/mayan/apps/linking/views.py +++ b/mayan/apps/linking/views.py @@ -12,7 +12,6 @@ from django.template import RequestContext from django.utils.translation import ugettext_lazy as _ from acls.models import AccessControlList -from common.utils import encapsulate from common.generics import ( AssignRemoveView, SingleObjectCreateView, SingleObjectEditView, SingleObjectListView @@ -145,14 +144,6 @@ class SmartLinkListView(SingleObjectListView): def get_extra_context(self): return { - 'extra_columns': [ - {'name': _('Dynamic label'), 'attribute': 'dynamic_label'}, - { - 'name': _('Enabled'), 'attribute': encapsulate( - lambda instance: two_state_template(instance.enabled) - ) - }, - ], 'hide_link': True, 'title': _('Smart links'), } @@ -183,15 +174,6 @@ class DocumentSmartLinkListView(SmartLinkListView): def get_extra_context(self): return { 'document': self.document, - 'extra_columns': ( - { - 'name': _('Label'), 'attribute': encapsulate( - lambda smart_link: smart_link.get_dynamic_label( - self.document - ) - ) - }, - ), 'hide_object': True, 'hide_link': True, 'object': self.document, @@ -272,17 +254,9 @@ class SmartLinkConditionListView(SingleObjectListView): def get_extra_context(self): return { - 'title': _('Conditions for smart link: %s') % self.get_smart_link(), - 'extra_columns': ( - { - 'name': _('Enabled'), - 'attribute': encapsulate( - lambda condition: two_state_template(condition.enabled) - ) - }, - ), 'hide_link': True, 'object': self.get_smart_link(), + 'title': _('Conditions for smart link: %s') % self.get_smart_link(), } def get_smart_link(self): diff --git a/mayan/apps/mailer/apps.py b/mayan/apps/mailer/apps.py index 64575a074b..158788e176 100644 --- a/mayan/apps/mailer/apps.py +++ b/mayan/apps/mailer/apps.py @@ -28,15 +28,11 @@ class MailerApp(MayanAppConfig): super(MailerApp, self).ready() SourceColumn( - source=LogEntry, - label='Date and time', - attribute='datetime' + source=LogEntry, label=_('Date and time'), attribute='datetime' ) SourceColumn( - source=LogEntry, - label='Message', - attribute='message' + source=LogEntry, label=_('Message'), attribute='message' ) ModelPermission.register( diff --git a/mayan/apps/metadata/apps.py b/mayan/apps/metadata/apps.py index 76e60071bd..49b620f1b4 100644 --- a/mayan/apps/metadata/apps.py +++ b/mayan/apps/metadata/apps.py @@ -13,7 +13,7 @@ from common import ( menu_setup, menu_sidebar, menu_tools ) from common.classes import ModelAttribute -from common.utils import encapsulate +from common.widgets import two_state_template from documents.models import Document, DocumentType from documents.search import document_search from documents.signals import post_document_type_change @@ -38,7 +38,7 @@ from .links import ( link_setup_metadata_type_edit, link_setup_metadata_type_list, link_documents_missing_required_metadata ) -from .models import DocumentTypeMetadataType, MetadataType +from .models import DocumentMetadata, DocumentTypeMetadataType, MetadataType from .permissions import ( permission_metadata_document_add, permission_metadata_document_edit, permission_metadata_document_remove, permission_metadata_document_view @@ -94,9 +94,16 @@ class MetadataApp(MayanAppConfig): SourceColumn( source=Document, label=_('Metadata'), - attribute=encapsulate( - lambda document: get_metadata_string(document) - ) + func=lambda context: get_metadata_string(context['object']) + ) + + SourceColumn( + source=DocumentMetadata, label=_('Value'), + attribute='value' + ) + SourceColumn( + source=DocumentMetadata, label=_('Required'), + func=lambda context: two_state_template(context['object'].is_required) ) app.conf.CELERY_QUEUES.append( diff --git a/mayan/apps/metadata/models.py b/mayan/apps/metadata/models.py index c4bb1b831b..75929a8552 100644 --- a/mayan/apps/metadata/models.py +++ b/mayan/apps/metadata/models.py @@ -103,6 +103,10 @@ class DocumentMetadata(models.Model): return super(DocumentMetadata, self).delete(*args, **kwargs) + @property + def is_required(self): + return self.metadata_type in self.document.document_type.metadata.filter(required=True) + class Meta: unique_together = ('document', 'metadata_type') verbose_name = _('Document metadata') diff --git a/mayan/apps/metadata/views.py b/mayan/apps/metadata/views.py index 35cb5aabe5..66673da4c1 100644 --- a/mayan/apps/metadata/views.py +++ b/mayan/apps/metadata/views.py @@ -11,7 +11,6 @@ from django.utils.http import urlencode from django.utils.translation import ugettext_lazy as _, ungettext from acls.models import AccessControlList -from common.utils import encapsulate from common.generics import ( AssignRemoveView, SingleObjectCreateView, SingleObjectDeleteView, SingleObjectEditView, SingleObjectListView @@ -495,18 +494,9 @@ class DocumentMetadataListView(SingleObjectListView): def get_extra_context(self): document = self.get_document() return { - 'title': _('Metadata for document: %s') % document, - 'extra_columns': ( - {'name': _('Value'), 'attribute': 'value'}, - { - 'name': _('Required'), - 'attribute': encapsulate( - lambda metadata: metadata.metadata_type in document.document_type.metadata.filter(required=True) - ) - } - ), 'hide_link': True, 'object': document, + 'title': _('Metadata for document: %s') % document, } def get_queryset(self): diff --git a/mayan/apps/navigation/classes.py b/mayan/apps/navigation/classes.py index 0a580f53dc..9841c8b531 100644 --- a/mayan/apps/navigation/classes.py +++ b/mayan/apps/navigation/classes.py @@ -281,11 +281,13 @@ class SourceColumn(object): # unhashable type: list return () - def __init__(self, source, label, attribute): + def __init__(self, source, label, attribute=None, func=None): + self.source = source + self.label = label + self.attribute = attribute + self.func = func self.__class__._registry.setdefault(source, []) - self.__class__._registry[source].append( - {'label': label, 'attribute': attribute} - ) + self.__class__._registry[source].append(self) class CombinedSource(object): diff --git a/mayan/apps/navigation/templatetags/navigation_tags.py b/mayan/apps/navigation/templatetags/navigation_tags.py index dccb73187f..7b3c3b4a31 100644 --- a/mayan/apps/navigation/templatetags/navigation_tags.py +++ b/mayan/apps/navigation/templatetags/navigation_tags.py @@ -2,6 +2,8 @@ from __future__ import unicode_literals from django.template import Library +from common.utils import return_attrib + from ..classes import Menu, SourceColumn from ..forms import MultiItemForm @@ -58,3 +60,11 @@ def get_source_columns(source): pass return SourceColumn.get_for_source(source) + + +@register.simple_tag(takes_context=True) +def source_column_resolve(context, column): + if column.attribute: + return return_attrib(context['object'], column.attribute) + elif column.func: + return column.func(context=context) diff --git a/mayan/apps/ocr/apps.py b/mayan/apps/ocr/apps.py index e7013314b8..9d3cc2ffd7 100644 --- a/mayan/apps/ocr/apps.py +++ b/mayan/apps/ocr/apps.py @@ -13,7 +13,6 @@ from common import ( MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary, menu_tools ) -from common.utils import encapsulate from documents.models import Document, DocumentType, DocumentVersion from documents.search import document_search from documents.signals import post_version_upload @@ -70,9 +69,7 @@ class OCRApp(MayanAppConfig): SourceColumn( source=DocumentVersionOCRError, label=_('Document'), - attribute=encapsulate( - lambda entry: document_link(entry.document_version.document) - ) + func=lambda context: document_link(context['object'].document_version.document) ) SourceColumn( source=DocumentVersionOCRError, label=_('Added'), diff --git a/mayan/apps/smart_settings/apps.py b/mayan/apps/smart_settings/apps.py index e16639d2db..cc347428dd 100644 --- a/mayan/apps/smart_settings/apps.py +++ b/mayan/apps/smart_settings/apps.py @@ -7,7 +7,6 @@ from django.apps import apps from django.utils.translation import ugettext_lazy as _ from common import MayanAppConfig, menu_setup, menu_object -from common.utils import encapsulate from common.widgets import exists_widget from navigation import SourceColumn @@ -29,20 +28,20 @@ class SmartSettingsApp(MayanAppConfig): SourceColumn( source=Namespace, label=_('Setting count'), - attribute=encapsulate(lambda instance: len(instance.settings)) + func=lambda context: len(context['object'].settings) ) SourceColumn( source=Setting, label=_('Name'), - attribute=encapsulate(lambda instance: setting_widget(instance)) + func=lambda context: setting_widget(context['object']) ) SourceColumn( source=Setting, label=_('Value'), attribute='serialized_value' ) SourceColumn( source=Setting, label=_('Found in path'), - attribute=encapsulate( - lambda instance: exists_widget(instance.value) if instance.is_path else _('n/a') - ) + func=lambda context: exists_widget( + context['object'].value + ) if context['object'].is_path else _('n/a') ) menu_object.bind_links( diff --git a/mayan/apps/sources/apps.py b/mayan/apps/sources/apps.py index e218560db0..96c879bbd0 100644 --- a/mayan/apps/sources/apps.py +++ b/mayan/apps/sources/apps.py @@ -9,7 +9,6 @@ from common import ( menu_sidebar, menu_setup ) from common.signals import post_initial_setup, post_upgrade -from common.utils import encapsulate from converter.links import link_transformation_list from documents.models import Document from documents.signals import post_version_upload @@ -32,8 +31,8 @@ from .links import ( link_upload_version ) from .models import ( - POP3Email, IMAPEmail, Source, StagingFolderSource, WatchFolderSource, - WebFormSource + POP3Email, IMAPEmail, Source, SourceLog, StagingFolderSource, + WatchFolderSource, WebFormSource ) from .widgets import staging_file_thumbnail @@ -61,15 +60,24 @@ class SourcesApp(MayanAppConfig): SourceColumn( source=StagingFile, label=_('Thumbnail'), - attribute=encapsulate( - lambda staging_file: staging_file_thumbnail( - staging_file, - gallery_name='sources:staging_list', - title=staging_file.filename, size='100' - ) + func=lambda context: staging_file_thumbnail( + context['object'], + gallery_name='sources:staging_list', + title=context['object'].filename, size='100' ) ) + SourceColumn( + source=SourceLog, + label=_('Date time'), + func=lambda context: context['object'].datetime + ) + SourceColumn( + source=SourceLog, + label=_('Message'), + func=lambda context: context['object'].message + ) + app.conf.CELERY_QUEUES.extend( ( Queue( diff --git a/mayan/apps/sources/views.py b/mayan/apps/sources/views.py index 066f2a7aed..5daa981b8a 100644 --- a/mayan/apps/sources/views.py +++ b/mayan/apps/sources/views.py @@ -58,16 +58,6 @@ class SourceLogListView(SingleObjectListView): def get_extra_context(self): return { - 'extra_columns': ( - { - 'name': _('Date time'), - 'attribute': encapsulate(lambda entry: entry.datetime) - }, - { - 'name': _('Message'), - 'attribute': encapsulate(lambda entry: entry.message) - }, - ), 'hide_object': True, 'object': self.get_source(), 'title': _('Log entries for source: %s') % self.get_source(), @@ -494,7 +484,7 @@ class SetupSourceListView(SingleObjectListView): queryset = Source.objects.select_subclasses() extra_context = { - 'extra_columns': [ + 'extra_columns': ( { 'name': _('Type'), 'attribute': encapsulate(lambda entry: entry.class_fullname()) @@ -505,7 +495,7 @@ class SetupSourceListView(SingleObjectListView): lambda entry: two_state_template(entry.enabled) ) }, - ], + ), 'hide_link': True, 'title': _('Sources'), } diff --git a/mayan/apps/tags/apps.py b/mayan/apps/tags/apps.py index 4563497502..73dd50f04e 100644 --- a/mayan/apps/tags/apps.py +++ b/mayan/apps/tags/apps.py @@ -9,7 +9,6 @@ from common import ( MayanAppConfig, menu_facet, menu_secondary, menu_object, menu_main, menu_multi_item, menu_sidebar ) -from common.utils import encapsulate from documents.models import Document from documents.search import document_search from navigation import CombinedSource, SourceColumn @@ -55,14 +54,18 @@ class TagsApp(MayanAppConfig): SourceColumn( source=Document, label=_('Tags'), - attribute=encapsulate( - lambda document: widget_inline_tags(document) - ) + func=lambda context: widget_inline_tags(context['object']) ) SourceColumn( source=Tag, label=_('Preview'), - attribute=encapsulate(lambda tag: widget_single_tag(tag)) + func=lambda context: widget_single_tag(context['object']) + ) + SourceColumn( + source=Tag, label=_('Documents'), + func=lambda context: context['object'].get_document_count( + user=context['request'].user + ) ) document_search.add_model_field(field='tags__label', label=_('Tags')) diff --git a/mayan/apps/tags/models.py b/mayan/apps/tags/models.py index 33bfc5996b..79f2f2b128 100644 --- a/mayan/apps/tags/models.py +++ b/mayan/apps/tags/models.py @@ -1,12 +1,16 @@ -from __future__ import unicode_literals +from __future__ import absolute_import, unicode_literals from django.db import models +from django.core.exceptions import PermissionDenied from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from colorful.fields import RGBColorField +from acls.models import AccessControlList from documents.models import Document +from documents.permissions import permission_document_view +from permissions import Permission @python_2_unicode_compatible @@ -25,3 +29,15 @@ class Tag(models.Model): def __str__(self): return self.label + + def get_document_count(self, user): + queryset = self.documents + + try: + Permission.check_permissions(user, (permission_document_view,)) + except PermissionDenied: + queryset = AccessControlList.objects.filter_by_access( + permission_document_view, user, queryset + ) + + return queryset.count() diff --git a/mayan/apps/tags/views.py b/mayan/apps/tags/views.py index 13af4e7766..16bef84245 100644 --- a/mayan/apps/tags/views.py +++ b/mayan/apps/tags/views.py @@ -12,7 +12,6 @@ from django.template import RequestContext from django.utils.translation import ugettext_lazy as _, ungettext from acls.models import AccessControlList -from common.utils import encapsulate from common.views import ( SingleObjectCreateView, SingleObjectEditView, SingleObjectListView ) @@ -126,33 +125,9 @@ def tag_multiple_attach(request): class TagListView(SingleObjectListView): object_permission = permission_tag_view - @staticmethod - def get_document_count(instance, user): - queryset = instance.documents - - try: - Permission.check_permissions(user, (permission_document_view,)) - except PermissionDenied: - queryset = AccessControlList.objects.filter_by_access( - permission_document_view, user, queryset - ) - - return queryset.count() - def get_extra_context(self): return { - 'extra_columns': ( - { - 'name': _('Documents'), - 'attribute': encapsulate( - lambda instance: TagListView.get_document_count( - instance=instance, user=self.request.user - ) - ) - }, - ), 'hide_link': True, - 'title': _('Tags'), } @@ -282,16 +257,6 @@ class DocumentTagListView(TagListView): def get_extra_context(self): return { - 'extra_columns': [ - { - 'name': _('Documents'), - 'attribute': encapsulate( - lambda instance: TagListView.get_document_count( - instance=instance, user=self.request.user - ) - ) - }, - ], 'hide_link': True, 'object': self.document, 'title': _('Tags for document: %s') % self.document, diff --git a/mayan/apps/user_management/apps.py b/mayan/apps/user_management/apps.py index e730fb773c..d5d09d7e91 100644 --- a/mayan/apps/user_management/apps.py +++ b/mayan/apps/user_management/apps.py @@ -8,7 +8,9 @@ from actstream import registry from common import menu_multi_item, menu_object, menu_secondary, menu_setup from common.apps import MayanAppConfig +from common.widgets import two_state_template from metadata import MetadataLookup +from navigation import SourceColumn from rest_api.classes import APIEndPoint from .links import ( @@ -33,6 +35,25 @@ class UserManagementApp(MayanAppConfig): MetadataLookup(description=_('All the groups.'), name='group', value=Group.objects.all()) MetadataLookup(description=_('All the users.'), name='users', value=get_user_model().objects.all()) + SourceColumn( + source=Group, label=_('Members'), attribute='user_set.count' + ) + + SourceColumn( + source=User, label=_('Full name'), attribute='get_full_name' + ) + SourceColumn( + source=User, label=_('Email'), attribute='email' + ) + SourceColumn( + source=User, label=_('Active'), + func=lambda context: two_state_template(context['object'].is_active) + ) + SourceColumn( + source=User, label=_('Has usable password?'), + func=lambda context: two_state_template(context['object'].has_usable_password()) + ) + menu_multi_item.bind_links( links=(link_group_multiple_delete,), sources=('user_management:group_list',) diff --git a/mayan/apps/user_management/views.py b/mayan/apps/user_management/views.py index 783d8090a9..1df5c113c2 100644 --- a/mayan/apps/user_management/views.py +++ b/mayan/apps/user_management/views.py @@ -10,7 +10,6 @@ from django.shortcuts import get_object_or_404, render_to_response from django.template import RequestContext from django.utils.translation import ugettext_lazy as _ -from common.utils import encapsulate from common.views import ( AssignRemoveView, SingleObjectCreateView, SingleObjectEditView, SingleObjectListView @@ -31,30 +30,6 @@ class UserListView(SingleObjectListView): def get_extra_context(self): return { - 'extra_columns': ( - { - 'name': _('Full name'), - 'attribute': 'get_full_name' - }, - { - 'name': _('Email'), - 'attribute': 'email' - }, - { - 'name': _('Active'), - 'attribute': encapsulate( - lambda user: two_state_template(user.is_active) - ), - }, - { - 'name': _('Has usable password?'), - 'attribute': encapsulate( - lambda user: two_state_template( - user.has_usable_password() - ) - ), - }, - ), 'hide_link': True, 'title': _('Users'), } @@ -301,8 +276,8 @@ class GroupEditView(SingleObjectEditView): class GroupListView(SingleObjectListView): extra_context = { - 'title': _('Groups'), 'hide_link': True, + 'title': _('Groups'), 'extra_columns': [ { 'name': _('Members'),