diff --git a/HISTORY.rst b/HISTORY.rst index 6632a726ba..d40e521239 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -109,6 +109,9 @@ - Improve statistics subclassing. Split class module into classes and renderers. - Sort facet link, object, secondady and sidebar actions. +- Add support for extended templates when there are no results. +- Add help messages and useful links to several apps when there + are no results available. 3.0.3 (2018-08-17) ================== diff --git a/mayan/apps/acls/views.py b/mayan/apps/acls/views.py index 3e4c8d60de..efd3becad7 100644 --- a/mayan/apps/acls/views.py +++ b/mayan/apps/acls/views.py @@ -6,6 +6,7 @@ import logging from django.contrib.contenttypes.models import ContentType from django.http import Http404, HttpResponseRedirect from django.shortcuts import get_object_or_404 +from django.template import RequestContext from django.urls import reverse from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy as _ @@ -18,6 +19,8 @@ from permissions import PermissionNamespace, Permission from permissions.models import StoredPermission from .classes import ModelPermission +from .icons import icon_acl_list +from .links import link_acl_create from .models import AccessControlList from .permissions import permission_acl_edit, permission_acl_view @@ -135,6 +138,19 @@ class ACLListView(SingleObjectListView): def get_extra_context(self): return { 'hide_object': True, + 'no_results_icon': icon_acl_list, + 'no_results_main_link': link_acl_create.resolve( + context=RequestContext( + self.request, {'resolved_object': self.content_object} + ) + ), + 'no_results_title': _( + 'There are no ACLs for this object' + ), + 'no_results_text': _( + 'ACL stands for Access Control List and is a precise method ' + ' to control user access to objects in the system.' + ), 'object': self.content_object, 'title': _('Access control lists for: %s' % self.content_object), } diff --git a/mayan/apps/appearance/templates/appearance/generic_form_subtemplate.html b/mayan/apps/appearance/templates/appearance/generic_form_subtemplate.html index 8eb688f6ba..c9e3b313f2 100644 --- a/mayan/apps/appearance/templates/appearance/generic_form_subtemplate.html +++ b/mayan/apps/appearance/templates/appearance/generic_form_subtemplate.html @@ -58,7 +58,11 @@ {% include 'appearance/generic_form_instance.html' %} {% empty %} - {% if form_empty_label %}{{ form_empty_label }}{% else %}{% trans 'No results' %}{% endif %} + {% if form_empty_label %}{{ form_empty_label }}{% else %} + + {% include 'appearance/no_results.html' %} + + {% endif %} {% endfor %} @@ -72,23 +76,26 @@ {% include 'appearance/generic_form_instance.html' %} {% endif %} {% if not read_only %} -
- + {% if previous %} +   + {% if cancel_label %}{{ cancel_label }}{% else %}{% trans 'Cancel' %}{% endif %} + {% endif %} - {% if submit_label %}{{ submit_label }}{% else %}{% if object %}{% trans 'Save' %}{% else %}{% trans 'Submit' %}{% endif %}{% endif %} - - {% if previous %} -   - {% if cancel_label %}{{ cancel_label }}{% else %}{% trans 'Cancel' %}{% endif %} - - {% endif %} -
+ + {% endif %} {% endif %} diff --git a/mayan/apps/appearance/templates/appearance/generic_list_horizontal.html b/mayan/apps/appearance/templates/appearance/generic_list_horizontal.html index 91a99cf10b..94c10d0dea 100644 --- a/mayan/apps/appearance/templates/appearance/generic_list_horizontal.html +++ b/mayan/apps/appearance/templates/appearance/generic_list_horizontal.html @@ -14,7 +14,9 @@ {% for object_navigation_links in resolved_links %} {% include 'navigation/generic_navigation.html' %} {% empty %} -

{% trans 'No results' %}

+

+ {% include 'appearance/no_results.html' %} +

{% endfor %} {% endwith %} diff --git a/mayan/apps/appearance/templates/appearance/generic_list_items_subtemplate.html b/mayan/apps/appearance/templates/appearance/generic_list_items_subtemplate.html index 93bf83dbec..a8559717b4 100644 --- a/mayan/apps/appearance/templates/appearance/generic_list_items_subtemplate.html +++ b/mayan/apps/appearance/templates/appearance/generic_list_items_subtemplate.html @@ -102,7 +102,7 @@ {% empty %}
-

{% trans 'No results' %}

+ {% include 'appearance/no_results.html' %}
{% endfor %} diff --git a/mayan/apps/appearance/templates/appearance/generic_list_subtemplate.html b/mayan/apps/appearance/templates/appearance/generic_list_subtemplate.html index d7fcc5956d..8b05503c3a 100644 --- a/mayan/apps/appearance/templates/appearance/generic_list_subtemplate.html +++ b/mayan/apps/appearance/templates/appearance/generic_list_subtemplate.html @@ -108,7 +108,11 @@ {% endif %} {% empty %} - {% trans 'No results' %} + + + {% include 'appearance/no_results.html' %} + + {% endfor %} diff --git a/mayan/apps/appearance/templates/appearance/no_results.html b/mayan/apps/appearance/templates/appearance/no_results.html new file mode 100644 index 0000000000..53bd7668e8 --- /dev/null +++ b/mayan/apps/appearance/templates/appearance/no_results.html @@ -0,0 +1,38 @@ +{% load i18n %} + +
+
+ {% if no_results_icon %} + {{ no_results_icon.render }} + {% else %} + + {% endif %} +
+ + {% trans 'No results' as default_title %} +

{{ no_results_title|default:default_title }}

+ {% if no_results_text %} +

{{ no_results_text }}

+ {% endif %} + + {% if no_results_main_link %} +
+ {% with no_results_main_link as link %} + {% with 'btn btn-primary btn-sm' as link_classes %} + {% include 'navigation/generic_subnavigation.html' %} + {% endwith %} + {% endwith %} +
+ {% endif %} + + {% if no_results_secondary_links %} +
+ {% for link in no_results_secondary_links %} + {% with 'btn btn-default btn-sm' as link_classes %} + {% include 'navigation/generic_subnavigation.html' %} + {% endwith %} + {% endfor %} +
+ {% endif %} +
+ diff --git a/mayan/apps/cabinets/icons.py b/mayan/apps/cabinets/icons.py index 5b77a6786c..bdcf84e25c 100644 --- a/mayan/apps/cabinets/icons.py +++ b/mayan/apps/cabinets/icons.py @@ -2,5 +2,6 @@ from __future__ import absolute_import, unicode_literals from appearance.classes import Icon +icon_cabinet = Icon(driver_name='fontawesome', symbol='columns') icon_cabinet_create = Icon(driver_name='fontawesome', symbol='plus') icon_cabinet_list = Icon(driver_name='fontawesome', symbol='columns') diff --git a/mayan/apps/cabinets/views.py b/mayan/apps/cabinets/views.py index 7466b27ba2..3bd7e9ab74 100644 --- a/mayan/apps/cabinets/views.py +++ b/mayan/apps/cabinets/views.py @@ -4,6 +4,7 @@ import logging from django.contrib import messages from django.shortcuts import get_object_or_404 +from django.template import RequestContext from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _, ungettext @@ -17,6 +18,10 @@ from documents.models import Document from documents.views import DocumentListView from .forms import CabinetListForm +from .icons import icon_cabinet +from .links import ( + link_cabinet_add_document, link_cabinet_child_add, link_cabinet_create +) from .models import Cabinet from .permissions import ( permission_cabinet_add_document, permission_cabinet_create, @@ -108,6 +113,18 @@ class CabinetDetailView(DocumentListView): jstree_data(node=cabinet.get_root(), selected_node=cabinet) ), 'list_as_items': True, + 'no_results_icon': icon_cabinet, + 'no_results_main_link': link_cabinet_child_add.resolve( + context=RequestContext( + request=self.request, dict_={'object': cabinet} + ) + ), + 'no_results_text': _( + 'Cabinets level can contain documents or other ' + 'cabinet sub levels. Documents can be added from ' + 'the document\'s cabinet section.' + ), + 'no_results_title': _('This cabinet level is empty'), 'object': cabinet, 'title': _('Details of cabinet: %s') % cabinet.get_full_path(), } @@ -151,6 +168,16 @@ class CabinetListView(SingleObjectListView): return { 'hide_link': True, 'title': _('Cabinets'), + 'no_results_icon': icon_cabinet, + 'no_results_main_link': link_cabinet_create.resolve( + context=RequestContext(request=self.request) + ), + 'no_results_text': _( + 'Cabinets are a multi-level method to organize ' + 'documents. Each cabinet can contain documents as ' + 'well as other sub level cabinets.' + ), + 'no_results_title': _('No cabinets available'), } def get_object_list(self): @@ -175,6 +202,18 @@ class DocumentCabinetListView(CabinetListView): def get_extra_context(self): return { 'hide_link': True, + 'no_results_icon': icon_cabinet, + 'no_results_main_link': link_cabinet_add_document.resolve( + context=RequestContext( + request=self.request, dict_={'object': self.document} + ) + ), + 'no_results_text': _( + 'Documents can be added to many cabinets.' + ), + 'no_results_title': _( + 'This document is not in any cabinet' + ), 'object': self.document, 'title': _('Cabinets containing document: %s') % self.document, } diff --git a/mayan/apps/checkouts/views.py b/mayan/apps/checkouts/views.py index 8623bc7b73..40acbcf7bd 100644 --- a/mayan/apps/checkouts/views.py +++ b/mayan/apps/checkouts/views.py @@ -17,6 +17,7 @@ from common.utils import encapsulate from .exceptions import DocumentAlreadyCheckedOut, DocumentNotCheckedOut from .forms import DocumentCheckoutForm, DocumentCheckoutDefailForm +from .icons import icon_checkout_info from .models import DocumentCheckout from .permissions import ( permission_document_checkin, permission_document_checkin_override, @@ -103,6 +104,13 @@ class CheckoutListView(DocumentListView): ) }, ), + 'no_results_icon': icon_checkout_info, + 'no_results_text': _( + 'Checking out a document blocks certain document ' + 'operations for a predetermined amount of ' + 'time.' + ), + 'no_results_title': _('No documents have been checked out'), } ) return context diff --git a/mayan/apps/common/views.py b/mayan/apps/common/views.py index 4db08c68c5..0fa384192f 100644 --- a/mayan/apps/common/views.py +++ b/mayan/apps/common/views.py @@ -29,6 +29,7 @@ from .generics import ( # NOQA SingleObjectDynamicFormEditView, SingleObjectDownloadView, SingleObjectEditView, SingleObjectListView, SimpleView ) +from .icons import icon_setup from .menus import menu_tools, menu_setup from .permissions_runtime import permission_error_log_view from .utils import check_version @@ -253,10 +254,18 @@ class SetupListView(TemplateView): data = super(SetupListView, self).get_context_data(**kwargs) context = RequestContext(self.request) context['request'] = self.request - data.update({ - 'resolved_links': menu_setup.resolve(context=context), - 'title': _('Setup items'), - }) + data.update( + { + 'no_results_icon': icon_setup, + 'no_results_label': _('No setup options available.'), + 'no_results_text': _( + 'No results here means that don\'t have the required ' + 'permissions to perform administrative task.' + ), + 'resolved_links': menu_setup.resolve(context=context), + 'title': _('Setup items'), + } + ) return data diff --git a/mayan/apps/converter/icons.py b/mayan/apps/converter/icons.py new file mode 100644 index 0000000000..381d35d281 --- /dev/null +++ b/mayan/apps/converter/icons.py @@ -0,0 +1,5 @@ +from __future__ import absolute_import, unicode_literals + +from appearance.classes import Icon + +icon_transformation = Icon(driver_name='fontawesome', symbol='crop') diff --git a/mayan/apps/converter/links.py b/mayan/apps/converter/links.py index feaa19dcd6..066bb372ac 100644 --- a/mayan/apps/converter/links.py +++ b/mayan/apps/converter/links.py @@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from navigation import Link +from .icons import icon_transformation from .permissions import ( permission_transformation_create, permission_transformation_delete, permission_transformation_edit, permission_transformation_view @@ -43,6 +44,7 @@ link_transformation_edit = Link( text=_('Edit'), view='converter:transformation_edit' ) link_transformation_list = Link( + icon_class=icon_transformation, kwargs=get_kwargs_factory('resolved_object'), permissions=(permission_transformation_view,), text=_('Transformations'), view='converter:transformation_list' diff --git a/mayan/apps/converter/views.py b/mayan/apps/converter/views.py index aba4aced84..63d4fbaa1d 100644 --- a/mayan/apps/converter/views.py +++ b/mayan/apps/converter/views.py @@ -5,6 +5,7 @@ import logging from django.contrib.contenttypes.models import ContentType from django.http import Http404 from django.shortcuts import get_object_or_404 +from django.template import RequestContext from django.urls import reverse from django.utils.translation import ugettext_lazy as _ @@ -14,6 +15,8 @@ from common.views import ( SingleObjectListView ) +from .icons import icon_transformation +from .links import link_transformation_create from .models import Transformation from .permissions import ( permission_transformation_create, permission_transformation_delete, @@ -210,6 +213,18 @@ class TransformationListView(SingleObjectListView): 'hide_link': True, 'hide_object': True, 'navigation_object_list': ('content_object',), + 'no_results_icon': icon_transformation, + 'no_results_main_link': link_transformation_create.resolve( + context=RequestContext( + self.request, {'content_object': self.content_object,} + ) + ), + 'no_results_text': _( + 'Transformations allow changing the visual appearance ' + 'of documents without making permanent changes to the ' + 'document file themselves.' + ), + 'no_results_title': _('No transformations'), 'title': _('Transformations for: %s') % self.content_object, } diff --git a/mayan/apps/document_comments/views.py b/mayan/apps/document_comments/views.py index aee9bdc805..5f53a5ba69 100644 --- a/mayan/apps/document_comments/views.py +++ b/mayan/apps/document_comments/views.py @@ -1,6 +1,7 @@ from __future__ import absolute_import, unicode_literals from django.shortcuts import get_object_or_404 +from django.template import RequestContext from django.urls import reverse from django.utils.translation import ugettext_lazy as _ @@ -10,6 +11,8 @@ from common.generics import ( ) from documents.models import Document +from .icons import icon_comments_for_document +from .links import link_comment_add from .models import Comment from .permissions import ( permission_comment_create, permission_comment_delete, @@ -93,6 +96,15 @@ class DocumentCommentListView(SingleObjectListView): return { 'hide_link': True, 'hide_object': True, + 'no_results_icon': icon_comments_for_document, + 'no_results_text': _( + 'Document comments are timestamped text entries from users. ' + 'They are great for collaboration.' + ), + 'no_results_main_link': link_comment_add.resolve( + RequestContext(self.request, {'object': self.get_document()}) + ), + 'no_results_title': _('There are no comments'), 'object': self.get_document(), 'title': _('Comments for document: %s') % self.get_document(), } diff --git a/mayan/apps/document_signatures/views.py b/mayan/apps/document_signatures/views.py index 5c696e12df..bb3828aaef 100644 --- a/mayan/apps/document_signatures/views.py +++ b/mayan/apps/document_signatures/views.py @@ -6,6 +6,7 @@ from django.contrib import messages from django.core.files import File from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 +from django.template import RequestContext from django.urls import reverse from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy as _ @@ -24,6 +25,12 @@ from .forms import ( DocumentVersionSignatureCreateForm, DocumentVersionSignatureDetailForm ) +from .icons import icon_document_signature_list +from .links import ( + link_document_version_signature_detached_create, + link_document_version_signature_embedded_create, + link_document_version_signature_upload +) from .models import DetachedSignature, SignatureBaseModel from .permissions import ( permission_document_version_sign_detached, @@ -294,6 +301,31 @@ class DocumentVersionSignatureListView(SingleObjectListView): def get_extra_context(self): return { 'hide_object': True, + 'no_results_icon': icon_document_signature_list, + 'no_results_text': _( + 'Signatures help provide authorship evidence and tamper ' + 'detection. They are very secure and hard to ' + 'forge. A signature can be embedded as part of the document ' + 'itself or uploaded as a separate file.' + ), + 'no_results_secondary_links': [ + link_document_version_signature_detached_create.resolve( + RequestContext( + self.request, {'object': self.get_document_version()} + ) + ), + link_document_version_signature_embedded_create.resolve( + RequestContext( + self.request, {'object': self.get_document_version()} + ) + ), + link_document_version_signature_upload.resolve( + RequestContext( + self.request, {'object': self.get_document_version()} + ) + ), + ], + 'no_results_title': _('There are no signatures for this document.'), 'object': self.get_document_version(), 'title': _( 'Signatures for document version: %s' diff --git a/mayan/apps/document_states/icons.py b/mayan/apps/document_states/icons.py index b83609e401..209ef11e75 100644 --- a/mayan/apps/document_states/icons.py +++ b/mayan/apps/document_states/icons.py @@ -12,3 +12,12 @@ icon_tool_launch_all_workflows = Icon( icon_workflow_list = Icon( driver_name='fontawesome', symbol='sitemap' ) +icon_workflow_state = Icon( + driver_name='fontawesome', symbol='circle' +) +icon_workflow_state_action = Icon( + driver_name='fontawesome', symbol='code' +) +icon_workflow_transition = Icon( + driver_name='fontawesome', symbol='arrows-alt-h' +) diff --git a/mayan/apps/document_states/links.py b/mayan/apps/document_states/links.py index b8db3d4fab..be55901d8c 100644 --- a/mayan/apps/document_states/links.py +++ b/mayan/apps/document_states/links.py @@ -6,7 +6,8 @@ from navigation import Link from .icons import ( icon_document_workflow_instance_list, icon_setup_workflow_list, - icon_tool_launch_all_workflows, icon_workflow_list + icon_tool_launch_all_workflows, icon_workflow_list, + icon_workflow_state, icon_workflow_state_action, icon_workflow_transition ) from .permissions import ( permission_workflow_create, permission_workflow_delete, @@ -20,8 +21,8 @@ link_document_workflow_instance_list = Link( view='document_states:document_workflow_instance_list', ) link_setup_workflow_create = Link( - permissions=(permission_workflow_create,), text=_('Create workflow'), - view='document_states:setup_workflow_create' + icon_class=icon_workflow_list, permissions=(permission_workflow_create,), + text=_('Create workflow'), view='document_states:setup_workflow_create' ) link_setup_workflow_delete = Link( args='resolved_object.pk', permissions=(permission_workflow_delete,), @@ -57,13 +58,13 @@ link_setup_workflow_state_action_list = Link( view='document_states:setup_workflow_state_action_list', ) link_setup_workflow_state_action_selection = Link( - args='resolved_object.pk', permissions=(permission_workflow_edit,), - text=_('Create action'), + args='resolved_object.pk', icon_class=icon_workflow_state_action, + permissions=(permission_workflow_edit,), text=_('Create action'), view='document_states:setup_workflow_state_action_selection', ) link_setup_workflow_state_create = Link( - args='resolved_object.pk', permissions=(permission_workflow_edit,), - text=_('Create state'), + args='resolved_object.pk', icon_class=icon_workflow_state, + permissions=(permission_workflow_edit,), text=_('Create state'), view='document_states:setup_workflow_state_create', ) link_setup_workflow_state_delete = Link( @@ -76,12 +77,13 @@ link_setup_workflow_state_edit = Link( text=_('Edit'), view='document_states:setup_workflow_state_edit', ) link_setup_workflow_states = Link( - args='resolved_object.pk', permissions=(permission_workflow_view,), - text=_('States'), view='document_states:setup_workflow_state_list', + args='resolved_object.pk', icon_class=icon_workflow_state, + permissions=(permission_workflow_view,), text=_('States'), + view='document_states:setup_workflow_state_list', ) link_setup_workflow_transition_create = Link( - args='resolved_object.pk', permissions=(permission_workflow_edit,), - text=_('Create transition'), + args='resolved_object.pk', icon_class=icon_workflow_transition, + permissions=(permission_workflow_edit,), text=_('Create transition'), view='document_states:setup_workflow_transition_create', ) link_setup_workflow_transition_delete = Link( @@ -94,8 +96,8 @@ link_setup_workflow_transition_edit = Link( text=_('Edit'), view='document_states:setup_workflow_transition_edit', ) link_setup_workflow_transitions = Link( - args='resolved_object.pk', permissions=(permission_workflow_view,), - text=_('Transitions'), + args='resolved_object.pk', icon_class=icon_workflow_transition, + permissions=(permission_workflow_view,), text=_('Transitions'), view='document_states:setup_workflow_transition_list', ) link_tool_launch_all_workflows = Link( diff --git a/mayan/apps/document_states/views.py b/mayan/apps/document_states/views.py index c85085a2c7..1fa21f3243 100644 --- a/mayan/apps/document_states/views.py +++ b/mayan/apps/document_states/views.py @@ -6,6 +6,7 @@ from django.db import transaction from django.db.utils import IntegrityError from django.http import Http404, HttpResponseRedirect from django.shortcuts import get_object_or_404 +from django.template import RequestContext from django.urls import reverse, reverse_lazy from django.utils.translation import ugettext_lazy as _ @@ -27,6 +28,15 @@ from .forms import ( WorkflowPreviewForm, WorkflowStateActionDynamicForm, WorkflowStateForm, WorkflowTransitionForm, WorkflowTransitionTriggerEventRelationshipFormSet ) +from .icons import ( + icon_workflow_list, icon_workflow_state, icon_workflow_state_action, + icon_workflow_transition +) +from .links import ( + link_setup_workflow_create, link_setup_workflow_state_create, + link_setup_workflow_state_action_selection, + link_setup_workflow_transition_create +) from .models import ( Workflow, WorkflowInstance, WorkflowState, WorkflowStateAction, WorkflowTransition, WorkflowRuntimeProxy, WorkflowStateRuntimeProxy, @@ -138,13 +148,27 @@ class WorkflowInstanceTransitionView(FormView): # Setup class SetupWorkflowListView(SingleObjectListView): - extra_context = { - 'title': _('Workflows'), - 'hide_object': True, - } model = Workflow object_permission = permission_workflow_view + def get_extra_context(self): + return { + 'hide_object': True, + 'no_results_icon': icon_workflow_list, + 'no_results_main_link': link_setup_workflow_create.resolve( + context=RequestContext(request=self.request) + ), + 'no_results_text': _( + 'Workflows store a series for states and keep track of the ' + 'current state of a document. Transitions are used to change the ' + 'current state to a new one.' + ), + 'no_results_title': _( + 'No workflows have been defined.' + ), + 'title': _('Workflows'), + } + class SetupWorkflowCreateView(SingleObjectCreateView): form_class = WorkflowForm @@ -324,6 +348,21 @@ class SetupWorkflowStateActionListView(SingleObjectListView): return { 'hide_object': True, 'navigation_object_list': ('object', 'workflow'), + 'no_results_icon': icon_workflow_state_action, + 'no_results_main_link': link_setup_workflow_state_action_selection.resolve( + context=RequestContext( + request=self.request, dict_={ + 'object': self.get_workflow_state() + } + ) + ), + 'no_results_title': _( + 'There are no actions for this workflow state.' + ), + 'no_results_text': _( + 'Workflow state actions are macros that get executed when ' + 'enters or leaves the state in which they reside.' + ), 'object': self.get_workflow_state(), 'title': _( 'Actions for workflow state: %s' @@ -469,6 +508,18 @@ class SetupWorkflowStateListView(SingleObjectListView): def get_extra_context(self): return { 'hide_link': True, + 'no_results_icon': icon_workflow_state, + 'no_results_main_link': link_setup_workflow_state_create.resolve( + context=RequestContext( + self.request, {'object': self.get_workflow()} + ) + ), + 'no_results_title': _( + 'This workflow doesn\'t have any states' + ), + 'no_results_text': _( + 'Create states and link them using transitions.' + ), 'object': self.get_workflow(), 'title': _('States of workflow: %s') % self.get_workflow() } @@ -584,6 +635,19 @@ class SetupWorkflowTransitionListView(SingleObjectListView): def get_extra_context(self): return { 'hide_link': True, + 'no_results_icon': icon_workflow_transition, + 'no_results_main_link': link_setup_workflow_transition_create.resolve( + context=RequestContext( + self.request, {'object': self.get_workflow()} + ) + ), + 'no_results_text': _( + 'Create a transition and use it to move a workflow from ' + ' one state to another.' + ), + 'no_results_title': _( + 'This workflow doesn\'t have any transitions' + ), 'object': self.get_workflow(), 'title': _( 'Transitions of workflow: %s' @@ -606,7 +670,18 @@ class WorkflowListView(SingleObjectListView): def get_extra_context(self): return { 'hide_object': True, - 'title': _('Workflows') + 'no_results_main_link': link_setup_workflow_create.resolve( + context=RequestContext( + self.request, {} + ) + ), + 'no_results_title': _('There are no workflows'), + 'no_results_text': _( + 'Create some workflows and associated them with a document ' + 'type. Active workflows will be shown here and the documents ' + 'for which they are executing.' + ), + 'title': _('Workflows'), } def get_object_list(self): @@ -635,6 +710,13 @@ class WorkflowDocumentListView(DocumentListView): context = super(WorkflowDocumentListView, self).get_extra_context() context.update( { + 'no_results_title': _( + 'There are documents executing this workflow' + ), + 'no_results_text': _( + 'Associate a workflow with some document types and ' + 'documents of those types will be listed in this view.' + ), 'object': self.workflow, 'title': _('Documents with the workflow: %s') % self.workflow } @@ -653,14 +735,17 @@ class WorkflowStateDocumentListView(DocumentListView): { 'object': workflow_state, 'navigation_object_list': ('object', 'workflow'), - 'workflow': WorkflowRuntimeProxy.objects.get( - pk=workflow_state.workflow.pk + 'no_results_title': _( + 'There are documents in this workflow state' ), 'title': _( 'Documents in the workflow "%s", state "%s"' ) % ( workflow_state.workflow, workflow_state - ) + ), + 'workflow': WorkflowRuntimeProxy.objects.get( + pk=workflow_state.workflow.pk + ), } ) return context @@ -693,6 +778,17 @@ class WorkflowStateListView(SingleObjectListView): return { 'hide_columns': True, 'hide_link': True, + 'no_results_main_link': link_setup_workflow_state_create.resolve( + context=RequestContext( + self.request, {'object': self.get_workflow()} + ) + ), + 'no_results_title': _( + 'This workflow doesn\'t have any state.' + ), + 'no_results_text': _( + 'Create states and link them using transitions.' + ), 'object': self.get_workflow(), 'title': _('States of workflow: %s') % self.get_workflow() } @@ -751,6 +847,10 @@ class SetupWorkflowTransitionTriggerEventListView(FormView): 'form_display_mode_table': True, 'navigation_object_list': ('object', 'workflow'), 'object': self.get_object(), + 'subtitle': _( + 'Triggers are events that cause this transition to execute ' + 'automatically.' + ), 'title': _( 'Workflow transition trigger events for: %s' ) % self.get_object(), diff --git a/mayan/apps/events/views.py b/mayan/apps/events/views.py index 588db6778e..6e899d8e5f 100644 --- a/mayan/apps/events/views.py +++ b/mayan/apps/events/views.py @@ -5,6 +5,7 @@ from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from django.http import Http404, HttpResponseRedirect from django.shortcuts import get_object_or_404 +from django.template import RequestContext from django.urls import reverse from django.utils.translation import ugettext_lazy as _ @@ -19,6 +20,8 @@ from .classes import EventType, ModelEventType from .forms import ( EventTypeUserRelationshipFormSet, ObjectEventTypeUserRelationshipFormSet ) +from .icons import icon_user_notifications_list +from .links import link_event_types_subscriptions_list from .models import StoredEventType from .permissions import permission_events_view from .widgets import event_object_link @@ -109,6 +112,17 @@ class NotificationListView(SingleObjectListView): def get_extra_context(self): return { 'hide_object': True, + 'no_results_icon': icon_user_notifications_list, + 'no_results_main_link': link_event_types_subscriptions_list.resolve( + context=RequestContext( + self.request, {} + ) + ), + 'no_results_text': _( + 'Subscribe to global or object events to receive ' + 'notifications.' + ), + 'no_results_title': _('There are no notifications'), 'object': self.request.user, 'title': _('Notifications'), } diff --git a/mayan/apps/metadata/icons.py b/mayan/apps/metadata/icons.py index 12e4142ffd..653b9ea401 100644 --- a/mayan/apps/metadata/icons.py +++ b/mayan/apps/metadata/icons.py @@ -11,4 +11,5 @@ icon_document_metadata_edit_submit = Icon( icon_document_metadata_remove_submit = Icon( driver_name='fontawesome', symbol='minus' ) +icon_metadata = Icon(driver_name='fontawesome', symbol='pencil-alt') icon_metadata_view = Icon(driver_name='fontawesome', symbol='pencil-alt') diff --git a/mayan/apps/metadata/views.py b/mayan/apps/metadata/views.py index d1231e3518..93bdf51227 100644 --- a/mayan/apps/metadata/views.py +++ b/mayan/apps/metadata/views.py @@ -5,6 +5,7 @@ from django.contrib import messages from django.core.exceptions import ValidationError from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 +from django.template import RequestContext from django.urls import reverse, reverse_lazy from django.utils.encoding import force_text from django.utils.http import urlencode @@ -28,7 +29,11 @@ from .forms import ( ) from .icons import ( icon_document_metadata_add_submit, icon_document_metadata_edit_submit, - icon_document_metadata_remove_submit + icon_document_metadata_remove_submit, icon_metadata +) +from .links import ( + link_metadata_add, link_metadata_multiple_add, + link_setup_metadata_type_create ) from .models import DocumentMetadata, MetadataType from .permissions import ( @@ -262,8 +267,29 @@ class DocumentMetadataEditView(MultipleObjectFormActionView): def get_extra_context(self): queryset = self.get_queryset() + if queryset.count() == 1: + no_results_main_link = link_metadata_add.resolve( + context=RequestContext( + request=self.request, dict_={'object': queryset.first()} + ) + ) + else: + no_results_main_link = link_metadata_multiple_add.resolve( + context=RequestContext(request=self.request) + ) + no_results_main_link.url = '{}?id_list={}'.format( + no_results_main_link.url, id_list + ) + result = { 'form_display_mode_table': True, + 'no_results_icon': icon_metadata, + 'no_results_main_link': no_results_main_link, + 'no_results_text': _( + 'Add metadata types available for this document\'s type ' + 'and assign them corresponding values.' + ), + 'no_results_title': _('There is no metadata to edit'), 'submit_icon_class': icon_document_metadata_edit_submit, 'submit_label': _('Edit'), 'title': ungettext( @@ -371,9 +397,23 @@ class DocumentMetadataListView(SingleObjectListView): return { 'hide_link': True, 'object': document, + 'no_results_icon': icon_metadata, + 'no_results_main_link': link_metadata_add.resolve( + context=RequestContext( + request=self.request, dict_={'object': document} + ) + ), + 'no_results_text': _( + 'Add metadata types this document\'s type ' + 'to be able to add them to individual documents. ' + 'Once added to individual document, you can then edit their ' + 'values.' + ), + 'no_results_title': _('This document doesn\'t have any metadata'), 'title': _('Metadata for document: %s') % document, } + def get_object_list(self): return self.get_document().metadata.all() @@ -579,6 +619,19 @@ class MetadataTypeListView(SingleObjectListView): }, ), 'hide_link': True, + 'no_results_icon': icon_metadata, + 'no_results_main_link': link_setup_metadata_type_create.resolve( + context=RequestContext(request=self.request) + ), + 'no_results_text': _( + 'Metadata types are users defined properties that can be ' + 'assigned values. Once created they must be associated to ' + 'document types, either as optional or required, for each. ' + 'Setting a metadata type as required for a document type ' + 'will block the upload of documents of that type until a ' + 'metadata value is provided.' + ), + 'no_results_title': _('There are no metadata types'), 'title': _('Metadata types'), } diff --git a/mayan/apps/tags/views.py b/mayan/apps/tags/views.py index a03514a4c7..ced3ca0904 100644 --- a/mayan/apps/tags/views.py +++ b/mayan/apps/tags/views.py @@ -4,6 +4,7 @@ import logging from django.contrib import messages from django.shortcuts import get_object_or_404, reverse +from django.template import RequestContext from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _, ungettext @@ -17,7 +18,10 @@ from documents.views import DocumentListView from documents.permissions import permission_document_view from .forms import TagMultipleSelectionForm -from .icons import icon_tag_delete_submit, icon_tag_remove_submit +from .icons import ( + icon_menu_tags, icon_tag_delete_submit, icon_tag_remove_submit +) +from .links import link_tag_attach, link_tag_create from .models import Tag from .permissions import ( permission_tag_attach, permission_tag_create, permission_tag_delete, @@ -190,6 +194,17 @@ class TagListView(SingleObjectListView): return { 'hide_link': True, 'hide_object': True, + 'no_results_icon': icon_menu_tags, + 'no_results_text': _( + 'Tags are color coded properties that can be attached or ' + 'removed from documents.' + ), + 'no_results_title': _('No tags available'), + 'no_results_main_link': link_tag_create.resolve( + context=RequestContext( + self.request, {} + ) + ), 'title': _('Tags'), } @@ -237,6 +252,12 @@ class DocumentTagListView(TagListView): context.update( { 'hide_link': True, + 'no_results_title': _('Document has no tags attached'), + 'no_results_main_link': link_tag_attach.resolve( + context=RequestContext( + self.request, {'object': self.document} + ) + ), 'object': self.document, 'title': _('Tags for document: %s') % self.document, }