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 %}
+ {% 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 %}
+
+ {% 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,
}