Add support for sortable columns
Add a new argument to the SourceColumn class to flag a column as sortable. The SourceColum will generate a request querystring to be used as the table header archor href. The SingleObjectListView will capture the querystring and call the order_by on the queryset to sort it. Signed-off-by: Roberto Rosario <Roberto.Rosario@mayan-edms.com>
This commit is contained in:
@@ -72,7 +72,13 @@
|
||||
{{ object }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% source_column_resolve column=object|get_source_columns:"only_identifier" as column_value %}{{ column_value }}
|
||||
{% get_source_columns source=object only_identifier=True as source_column %}
|
||||
{% source_column_resolve column=source_column as column_value %}
|
||||
{% if source_column.is_absolute_url %}
|
||||
<a href="{{ object.get_absolute_url }}">{{ column_value }}</a>
|
||||
{% else %}
|
||||
{{ column_value }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</span>
|
||||
</label>
|
||||
@@ -82,7 +88,8 @@
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if not hide_columns %}
|
||||
{% for column in object|get_source_columns:"exclude_identifier" %}
|
||||
{% get_source_columns source=object exclude_identifier=True as source_columns %}
|
||||
{% for column in source_columns %}
|
||||
<div class="text-center" style="">{% source_column_resolve column=column as column_value %}{% if column_value != '' %}{{ column.label }}: {{ column_value }}{% endif %}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
@@ -66,8 +66,19 @@
|
||||
{% endif %}
|
||||
|
||||
{% if not hide_columns %}
|
||||
{% for column in object_list|get_source_columns %}
|
||||
<th>{{ column.label }}</th>
|
||||
{% get_source_columns source=object_list as source_columns %}
|
||||
{% for column in source_columns %}
|
||||
<th>
|
||||
{% if column.is_sortable %}
|
||||
<a href="{% get_sort_field_querystring column=column %}">{{ column.label }}
|
||||
{% if column.attribute == sort_field %}
|
||||
{% if icon_sort %}{{ icon_sort.render }}{% endif %}
|
||||
{% endif %}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ column.label }}
|
||||
{% endif %}
|
||||
</th>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
@@ -91,9 +102,22 @@
|
||||
{% endif %}
|
||||
{% if not hide_object %}
|
||||
<td>{% if not hide_link %}<a href="{{ object.get_absolute_url }}">{{ object }}</a>{% else %}{{ object }}{% endif %}</td>
|
||||
{% else %}
|
||||
{% get_source_columns source=object only_identifier=True as source_column %}
|
||||
{% source_column_resolve column=source_column as column_value %}
|
||||
{% if column_value %}
|
||||
<td>
|
||||
{% if source_column.is_absolute_url %}
|
||||
<a href="{{ object.get_absolute_url }}">{{ column_value }}</a>
|
||||
{% else %}
|
||||
{{ column_value }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if not hide_columns %}
|
||||
{% for column in object|get_source_columns %}
|
||||
{% get_source_columns source=object exclude_identifier=True as source_columns %}
|
||||
{% for column in source_columns %}
|
||||
<td>{% source_column_resolve column=column as column_value %}{{ column_value }}</td>{# Use explicit 'as column_value ' to force date rendering #}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% load common_tags %}
|
||||
|
||||
<div class="pull-left">
|
||||
<div class="btn-toolbar" role="toolbar">
|
||||
<div class="btn-group">
|
||||
{% if multi_item_actions %}
|
||||
<div class="btn-group">
|
||||
{% if multi_item_actions %}
|
||||
<a class="btn btn-default btn-sm check-all" data-checked=false data-icon-checked="fa fa-check-square" data-icon-unchecked="far fa-square" title="{% trans 'Select/Deselect all' %}">
|
||||
<i class="far fa-square"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a class="btn btn-default btn-sm" href="?_list_mode={% if list_as_items %}list{% else %}items{% endif %}" title="{% trans 'Toggle list display mode' %}">
|
||||
<i class="fa {% if list_as_items %}fa-list{% else %}fa-th-large{% endif %}"></i>
|
||||
{% endif %}
|
||||
<a class="btn btn-default btn-sm" href="{% get_list_mode_querystring %}" title="{% trans 'Toggle list display mode' %}">
|
||||
{% get_list_mode_icon as list_mode_icon %}{{ list_mode_icon.render }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,10 +22,15 @@ from django_downloadview import (
|
||||
from pure_pagination.mixins import PaginationMixin
|
||||
|
||||
from .forms import ChoiceForm
|
||||
from .icons import icon_assign_remove_add, icon_assign_remove_remove
|
||||
from .icons import (
|
||||
icon_assign_remove_add, icon_assign_remove_remove, icon_sort_down,
|
||||
icon_sort_up
|
||||
)
|
||||
from .literals import (
|
||||
TEXT_CHOICE_ITEMS, TEXT_CHOICE_LIST, TEXT_LIST_AS_ITEMS,
|
||||
TEXT_LIST_AS_ITEMS_PARAMETER
|
||||
TEXT_CHOICE_ITEMS, TEXT_CHOICE_LIST, TEXT_LIST_AS_ITEMS_VARIABLE_NAME,
|
||||
TEXT_LIST_AS_ITEMS_PARAMETER, TEXT_SORT_FIELD_PARAMETER,
|
||||
TEXT_SORT_FIELD_VARIABLE_NAME, TEXT_SORT_ORDER_CHOICE_ASCENDING,
|
||||
TEXT_SORT_ORDER_PARAMETER, TEXT_SORT_ORDER_VARIABLE_NAME
|
||||
)
|
||||
from .mixins import (
|
||||
DeleteExtraDataMixin, DynamicFormViewMixin, ExtraContextMixin,
|
||||
@@ -505,7 +510,7 @@ class SingleObjectListView(PaginationMixin, ViewPermissionCheckMixin, ObjectList
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(SingleObjectListView, self).get_context_data(**kwargs)
|
||||
|
||||
if context.get(TEXT_LIST_AS_ITEMS):
|
||||
if context.get(TEXT_LIST_AS_ITEMS_VARIABLE_NAME):
|
||||
default_mode = TEXT_CHOICE_ITEMS
|
||||
else:
|
||||
default_mode = TEXT_CHOICE_LIST
|
||||
@@ -514,15 +519,52 @@ class SingleObjectListView(PaginationMixin, ViewPermissionCheckMixin, ObjectList
|
||||
TEXT_LIST_AS_ITEMS_PARAMETER, default_mode
|
||||
)
|
||||
|
||||
context.update({TEXT_LIST_AS_ITEMS: list_mode == TEXT_CHOICE_ITEMS})
|
||||
print '@@ list_mode', list_mode
|
||||
|
||||
context.update(
|
||||
{
|
||||
TEXT_LIST_AS_ITEMS_VARIABLE_NAME: list_mode == TEXT_CHOICE_ITEMS,
|
||||
TEXT_SORT_FIELD_VARIABLE_NAME: self.get_sort_field(),
|
||||
TEXT_SORT_ORDER_VARIABLE_NAME: self.get_sort_order(),
|
||||
'icon_sort': self.get_sort_icon(),
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
def get_sort_field(self):
|
||||
return self.request.GET.get(TEXT_SORT_FIELD_PARAMETER)
|
||||
|
||||
def get_sort_icon(self):
|
||||
sort_order = self.get_sort_order()
|
||||
if not sort_order:
|
||||
return
|
||||
elif sort_order == TEXT_SORT_ORDER_CHOICE_ASCENDING:
|
||||
return icon_sort_down
|
||||
else:
|
||||
return icon_sort_up
|
||||
|
||||
def get_sort_order(self):
|
||||
return self.request.GET.get(TEXT_SORT_ORDER_PARAMETER)
|
||||
|
||||
def get_paginate_by(self, queryset):
|
||||
return setting_paginate_by.value
|
||||
|
||||
def get_queryset(self):
|
||||
self.field_name = self.get_sort_field()
|
||||
if self.get_sort_order() == TEXT_SORT_ORDER_CHOICE_ASCENDING:
|
||||
sort_order = ''
|
||||
else:
|
||||
sort_order = '-'
|
||||
|
||||
try:
|
||||
return super(SingleObjectListView, self).get_queryset()
|
||||
queryset = super(SingleObjectListView, self).get_queryset()
|
||||
except ImproperlyConfigured:
|
||||
self.queryset = self.get_object_list()
|
||||
return super(SingleObjectListView, self).get_queryset()
|
||||
queryset = super(SingleObjectListView, self).get_queryset()
|
||||
|
||||
if self.field_name:
|
||||
queryset = queryset.order_by(
|
||||
'{}{}'.format(sort_order, self.field_name)
|
||||
)
|
||||
|
||||
return queryset
|
||||
|
||||
@@ -24,6 +24,12 @@ icon_forum = Icon(
|
||||
icon_license = Icon(
|
||||
driver_name='fontawesome', symbol='certificate'
|
||||
)
|
||||
icon_list_mode_list = Icon(
|
||||
driver_name='fontawesome', symbol='list'
|
||||
)
|
||||
icon_list_mode_items = Icon(
|
||||
driver_name='fontawesome', symbol='th-large'
|
||||
)
|
||||
icon_menu_about = Icon(
|
||||
driver_name='fontawesome', symbol='info'
|
||||
)
|
||||
@@ -54,6 +60,8 @@ icon_social_paypal = Icon(
|
||||
icon_social_twitter = Icon(
|
||||
driver_name='fontawesomecss', css_classes='fab fa-twitter'
|
||||
)
|
||||
icon_sort_down = Icon(driver_name='fontawesome', symbol='sort-down')
|
||||
icon_sort_up = Icon(driver_name='fontawesome', symbol='sort-up')
|
||||
icon_source_code = Icon(driver_name='fontawesome', symbol='code-branch')
|
||||
icon_support = Icon(
|
||||
driver_name='fontawesome', symbol='phone'
|
||||
|
||||
@@ -13,9 +13,15 @@ MESSAGE_SQLITE_WARNING = _(
|
||||
PYPI_URL = 'https://pypi.python.org/pypi'
|
||||
|
||||
TEXT_LIST_AS_ITEMS_PARAMETER = '_list_mode'
|
||||
TEXT_LIST_AS_ITEMS = 'list_as_items'
|
||||
TEXT_LIST_AS_ITEMS_VARIABLE_NAME = 'list_as_items'
|
||||
TEXT_CHOICE_ITEMS = 'items'
|
||||
TEXT_CHOICE_LIST = 'list'
|
||||
TEXT_SORT_FIELD_PARAMETER = '_sort_field'
|
||||
TEXT_SORT_FIELD_VARIABLE_NAME = 'sort_field'
|
||||
TEXT_SORT_ORDER_CHOICE_ASCENDING = 'asc'
|
||||
TEXT_SORT_ORDER_CHOICE_DESCENDING = 'desc'
|
||||
TEXT_SORT_ORDER_PARAMETER = '_sort_order'
|
||||
TEXT_SORT_ORDER_VARIABLE_NAME = 'sort_order'
|
||||
|
||||
TIME_DELTA_UNIT_DAYS = 'days'
|
||||
TIME_DELTA_UNIT_HOURS = 'hours'
|
||||
|
||||
@@ -12,6 +12,7 @@ import mayan
|
||||
from mayan.apps.appearance.settings import setting_max_title_length
|
||||
|
||||
from ..classes import Collection
|
||||
from ..icons import icon_list_mode_items, icon_list_mode_list
|
||||
from ..literals import MESSAGE_SQLITE_WARNING
|
||||
from ..utils import check_for_sqlite, resolve_attribute
|
||||
|
||||
@@ -24,11 +25,59 @@ def check_sqlite():
|
||||
return MESSAGE_SQLITE_WARNING
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def common_calculate_title(context):
|
||||
if context.get('title'):
|
||||
return truncatechars(
|
||||
value=context.get('title'), arg=setting_max_title_length.value
|
||||
)
|
||||
else:
|
||||
if context.get('delete_view'):
|
||||
return _('Confirm delete')
|
||||
else:
|
||||
if context.get('form'):
|
||||
if context.get('object'):
|
||||
return _('Edit %s') % context.get('object')
|
||||
else:
|
||||
return _('Confirm')
|
||||
else:
|
||||
if context.get('read_only'):
|
||||
return _('Details for: %s') % context.get('object')
|
||||
else:
|
||||
if context.get('object'):
|
||||
return _('Edit: %s') % context.get('object')
|
||||
else:
|
||||
return _('Create')
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def get_collections():
|
||||
return Collection.get_all()
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def get_list_mode_icon(context):
|
||||
if context.get('list_as_items', False):
|
||||
return icon_list_mode_list
|
||||
else:
|
||||
return icon_list_mode_items
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def get_list_mode_querystring(context):
|
||||
# We do this to get an mutable copy we can modify
|
||||
querystring = context.request.GET.copy()
|
||||
|
||||
list_as_items = context.get('list_as_items', False)
|
||||
|
||||
if list_as_items:
|
||||
querystring['_list_mode'] = 'list'
|
||||
else:
|
||||
querystring['_list_mode'] = 'items'
|
||||
|
||||
return '?{}'.format(querystring.urlencode())
|
||||
|
||||
|
||||
@register.filter
|
||||
def get_type(value):
|
||||
return force_text(type(value))
|
||||
@@ -53,28 +102,3 @@ def render_subtemplate(context, template_name, template_context):
|
||||
new_context = Context(context.flatten())
|
||||
new_context.update(Context(template_context))
|
||||
return get_template(template_name).render(new_context.flatten())
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def common_calculate_title(context):
|
||||
if context.get('title'):
|
||||
return truncatechars(
|
||||
value=context.get('title'), arg=setting_max_title_length.value
|
||||
)
|
||||
else:
|
||||
if context.get('delete_view'):
|
||||
return _('Confirm delete')
|
||||
else:
|
||||
if context.get('form'):
|
||||
if context.get('object'):
|
||||
return _('Edit %s') % context.get('object')
|
||||
else:
|
||||
return _('Confirm')
|
||||
else:
|
||||
if context.get('read_only'):
|
||||
return _('Details for: %s') % context.get('object')
|
||||
else:
|
||||
if context.get('object'):
|
||||
return _('Edit: %s') % context.get('object')
|
||||
else:
|
||||
return _('Create')
|
||||
|
||||
@@ -233,15 +233,25 @@ class DocumentsApp(MayanAppConfig):
|
||||
document_page_thumbnail_widget = DocumentPageThumbnailWidget()
|
||||
|
||||
# Document
|
||||
SourceColumn(
|
||||
attribute='label', is_absolute_url=True, is_identifier=True,
|
||||
is_sortable=True, source=Document
|
||||
)
|
||||
SourceColumn(
|
||||
func=lambda context: document_page_thumbnail_widget.render(
|
||||
instance=context['object']
|
||||
), label=_('Thumbnail'), source=Document
|
||||
)
|
||||
SourceColumn(
|
||||
attribute='document_type', label=_('Type'), source=Document
|
||||
attribute='document_type', is_sortable=True, label=_('Type'),
|
||||
source=Document
|
||||
)
|
||||
SourceColumn(attribute='get_page_count', source=Document)
|
||||
SourceColumn(
|
||||
attribute='date_added', is_sortable=True, source=Document, views=(
|
||||
'documents:document_list_recent_added',
|
||||
)
|
||||
)
|
||||
|
||||
# DocumentPage
|
||||
SourceColumn(
|
||||
@@ -262,6 +272,10 @@ class DocumentsApp(MayanAppConfig):
|
||||
)
|
||||
|
||||
# DocumentType
|
||||
SourceColumn(
|
||||
attribute='label', is_identifier=True, is_sortable=True,
|
||||
source=DocumentType
|
||||
)
|
||||
SourceColumn(
|
||||
func=lambda context: context['object'].get_document_count(
|
||||
user=context['request'].user
|
||||
@@ -269,11 +283,19 @@ class DocumentsApp(MayanAppConfig):
|
||||
)
|
||||
|
||||
SourceColumn(
|
||||
attribute='enabled', source=DocumentTypeFilename,
|
||||
attribute='filename', is_identifier=True, is_sortable=True,
|
||||
source=DocumentTypeFilename
|
||||
)
|
||||
SourceColumn(
|
||||
attribute='enabled', is_sortable=True, source=DocumentTypeFilename,
|
||||
widget=TwoStateWidget
|
||||
)
|
||||
|
||||
# DeletedDocument
|
||||
SourceColumn(
|
||||
attribute='label', is_identifier=True, is_sortable=True,
|
||||
source=DeletedDocument
|
||||
)
|
||||
SourceColumn(
|
||||
func=lambda context: document_page_thumbnail_widget.render(
|
||||
instance=context['object']
|
||||
@@ -281,7 +303,7 @@ class DocumentsApp(MayanAppConfig):
|
||||
)
|
||||
|
||||
SourceColumn(
|
||||
attribute='document_type', source=DeletedDocument
|
||||
attribute='document_type', is_sortable=True, source=DeletedDocument
|
||||
)
|
||||
SourceColumn(
|
||||
attribute='get_rendered_deleted_date_time', source=DeletedDocument
|
||||
@@ -298,9 +320,15 @@ class DocumentsApp(MayanAppConfig):
|
||||
), label=_('Thumbnail'), source=DocumentVersion
|
||||
)
|
||||
SourceColumn(attribute='get_page_count', source=DocumentVersion)
|
||||
SourceColumn(attribute='mimetype', source=DocumentVersion)
|
||||
SourceColumn(attribute='encoding', source=DocumentVersion)
|
||||
SourceColumn(attribute='comment', source=DocumentVersion)
|
||||
SourceColumn(
|
||||
attribute='mimetype', is_sortable=True, source=DocumentVersion
|
||||
)
|
||||
SourceColumn(
|
||||
attribute='encoding', is_sortable=True, source=DocumentVersion
|
||||
)
|
||||
SourceColumn(
|
||||
attribute='comment', is_sortable=True, source=DocumentVersion
|
||||
)
|
||||
|
||||
# DuplicatedDocument
|
||||
SourceColumn(
|
||||
|
||||
@@ -115,6 +115,7 @@ class DocumentTypeListView(SingleObjectListView):
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'hide_link': True,
|
||||
'hide_object': True,
|
||||
'no_results_icon': icon_document_type_setup,
|
||||
'no_results_main_link': link_document_type_create.resolve(
|
||||
context=RequestContext(request=self.request)
|
||||
@@ -224,6 +225,7 @@ class DocumentTypeFilenameListView(SingleObjectListView):
|
||||
return {
|
||||
'document_type': self.get_document_type(),
|
||||
'hide_link': True,
|
||||
'hide_object': True,
|
||||
'navigation_object_list': ('document_type',),
|
||||
'no_results_icon': icon_document_type_filename,
|
||||
'no_results_main_link': link_document_type_filename_create.resolve(
|
||||
|
||||
@@ -84,6 +84,7 @@ class DocumentListView(SingleObjectListView):
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'hide_links': True,
|
||||
'hide_object': True,
|
||||
'list_as_items': True,
|
||||
'no_results_icon': icon_document_list,
|
||||
'no_results_text': _(
|
||||
@@ -963,9 +964,6 @@ class RecentAddedDocumentListView(DocumentListView):
|
||||
context = super(RecentAddedDocumentListView, self).get_extra_context()
|
||||
context.update(
|
||||
{
|
||||
'extra_columns': (
|
||||
{'name': _('Added'), 'attribute': 'date_added'},
|
||||
),
|
||||
'no_results_icon': icon_document_list_recent_added,
|
||||
'no_results_text': _(
|
||||
'This view will list the latest documents uploaded '
|
||||
|
||||
@@ -40,7 +40,7 @@ class EventsApp(MayanAppConfig):
|
||||
|
||||
SourceColumn(
|
||||
attribute='timestamp', is_identifier=True,
|
||||
label=_('Date and time'), source=Action
|
||||
is_sortable=True, label=_('Date and time'), source=Action
|
||||
)
|
||||
SourceColumn(
|
||||
func=widget_event_user_link, label=_('Actor'), source=Action
|
||||
@@ -65,9 +65,8 @@ class EventsApp(MayanAppConfig):
|
||||
)
|
||||
|
||||
SourceColumn(
|
||||
attribute='action.timestamp', is_identifier=True,
|
||||
label=_('Date and time'), source=Notification
|
||||
|
||||
attribute='action__timestamp', is_identifier=True,
|
||||
is_sortable=True, label=_('Date and time'), source=Notification
|
||||
)
|
||||
SourceColumn(
|
||||
func=widget_event_user_link, kwargs={'attribute': 'action'},
|
||||
@@ -77,15 +76,14 @@ class EventsApp(MayanAppConfig):
|
||||
func=widget_event_type_link, kwargs={'attribute': 'action'},
|
||||
label=_('Event'), source=Notification
|
||||
)
|
||||
|
||||
SourceColumn(
|
||||
func=widget_event_object_link, kwargs={
|
||||
'attribute': 'action.target'
|
||||
}, label=_('Target'), source=Notification
|
||||
)
|
||||
SourceColumn(
|
||||
attribute='read', label=_('Seen'), source=Notification,
|
||||
widget=TwoStateWidget
|
||||
attribute='read', is_sortable=True, label=_('Seen'),
|
||||
source=Notification, widget=TwoStateWidget
|
||||
)
|
||||
|
||||
menu_list_facet.bind_links(
|
||||
|
||||
@@ -154,17 +154,20 @@ class MetadataApp(MayanAppConfig):
|
||||
|
||||
SourceColumn(
|
||||
attribute='metadata_type', is_identifier=True,
|
||||
source=DocumentMetadata
|
||||
is_sortable=True, source=DocumentMetadata
|
||||
)
|
||||
SourceColumn(
|
||||
attribute='value', is_sortable=True, source=DocumentMetadata
|
||||
)
|
||||
SourceColumn(attribute='value', source=DocumentMetadata)
|
||||
SourceColumn(
|
||||
attribute='is_required', source=DocumentMetadata,
|
||||
widget=TwoStateWidget
|
||||
)
|
||||
SourceColumn(
|
||||
attribute='label', is_identifier=True, source=MetadataType
|
||||
attribute='label', is_identifier=True, is_sortable=True,
|
||||
source=MetadataType
|
||||
)
|
||||
SourceColumn(attribute='name', source=MetadataType)
|
||||
SourceColumn(attribute='name', is_sortable=True, source=MetadataType)
|
||||
|
||||
app.conf.task_queues.append(
|
||||
Queue('metadata', Exchange('metadata'), routing_key='metadata'),
|
||||
|
||||
@@ -17,17 +17,23 @@ from django.urls import Resolver404, resolve
|
||||
from django.utils.encoding import force_str, force_text
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.common.literals import (
|
||||
TEXT_SORT_FIELD_PARAMETER, TEXT_SORT_FIELD_VARIABLE_NAME,
|
||||
TEXT_SORT_ORDER_CHOICE_ASCENDING, TEXT_SORT_ORDER_CHOICE_DESCENDING,
|
||||
TEXT_SORT_ORDER_PARAMETER, TEXT_SORT_ORDER_VARIABLE_NAME
|
||||
)
|
||||
from mayan.apps.common.utils import resolve_attribute
|
||||
from mayan.apps.permissions import Permission
|
||||
|
||||
from .exceptions import NavigationError
|
||||
from .utils import get_current_view_name
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResolvedLink(object):
|
||||
def __init__(self, link, current_view):
|
||||
self.current_view = current_view
|
||||
def __init__(self, link, current_view_name):
|
||||
self.current_view_name = current_view_name
|
||||
self.disabled = False
|
||||
self.link = link
|
||||
self.url = '#'
|
||||
@@ -36,7 +42,7 @@ class ResolvedLink(object):
|
||||
|
||||
@property
|
||||
def active(self):
|
||||
return self.link.view == self.current_view
|
||||
return self.link.view == self.current_view_name
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
@@ -177,15 +183,8 @@ class Menu(object):
|
||||
logger.warning('No request variable, aborting menu resolution')
|
||||
return ()
|
||||
|
||||
current_path = request.META['PATH_INFO']
|
||||
|
||||
# Get sources: view name, view objects
|
||||
try:
|
||||
current_view = resolve(current_path).view_name
|
||||
except Resolver404:
|
||||
# Can't figure out which view corresponds to this URL.
|
||||
# Most likely it is an invalid URL.
|
||||
logger.warning('Can\'t figure out which view corresponds to this URL: %s; aborting menu resolution.', current_path)
|
||||
current_view_name = get_current_view_name(request=request)
|
||||
if not current_view_name:
|
||||
return ()
|
||||
|
||||
resolved_navigation_object_list = self.get_resolved_navigation_object_list(
|
||||
@@ -230,10 +229,10 @@ class Menu(object):
|
||||
|
||||
resolved_links = []
|
||||
# View links
|
||||
for link in self.bound_links.get(current_view, []):
|
||||
for link in self.bound_links.get(current_view_name, []):
|
||||
resolved_link = link.resolve(context=context)
|
||||
if resolved_link:
|
||||
if resolved_link.link not in self.unbound_links.get(current_view, ()):
|
||||
if resolved_link.link not in self.unbound_links.get(current_view_name, ()):
|
||||
resolved_links.append(resolved_link)
|
||||
|
||||
if resolved_links:
|
||||
@@ -349,7 +348,7 @@ class Link(object):
|
||||
request = Variable('request').resolve(context)
|
||||
|
||||
current_path = request.META['PATH_INFO']
|
||||
current_view = resolve(current_path).view_name
|
||||
current_view_name = resolve(current_path).view_name
|
||||
|
||||
# ACL is tested agains the resolved_object or just {{ object }} if not
|
||||
if not resolved_object:
|
||||
@@ -384,7 +383,9 @@ class Link(object):
|
||||
if not self.condition(context):
|
||||
return None
|
||||
|
||||
resolved_link = ResolvedLink(current_view=current_view, link=self)
|
||||
resolved_link = ResolvedLink(
|
||||
current_view_name=current_view_name, link=self
|
||||
)
|
||||
|
||||
if self.view:
|
||||
view_name = Variable('"{}"'.format(self.view))
|
||||
@@ -465,7 +466,7 @@ class Separator(Link):
|
||||
self.view = None
|
||||
|
||||
def resolve(self, *args, **kwargs):
|
||||
result = ResolvedLink(current_view=None, link=self)
|
||||
result = ResolvedLink(current_view_name=None, link=self)
|
||||
result.separator = True
|
||||
return result
|
||||
|
||||
@@ -498,7 +499,7 @@ class SourceColumn(object):
|
||||
return sorted(columns, key=lambda x: x.order)
|
||||
|
||||
@classmethod
|
||||
def get_for_source(cls, source, exclude_identifier=False, only_identifier=False):
|
||||
def get_for_source(cls, context, source, exclude_identifier=False, only_identifier=False):
|
||||
try:
|
||||
result = SourceColumn.sort(columns=cls._registry[source])
|
||||
except KeyError:
|
||||
@@ -521,16 +522,26 @@ class SourceColumn(object):
|
||||
result = ()
|
||||
|
||||
if exclude_identifier:
|
||||
return [item for item in result if not item.is_identifier]
|
||||
result = [item for item in result if not item.is_identifier]
|
||||
else:
|
||||
if only_identifier:
|
||||
for item in result:
|
||||
if item.is_identifier:
|
||||
return item
|
||||
else:
|
||||
return result
|
||||
return None
|
||||
|
||||
def __init__(self, source, attribute=None, empty_value=None, func=None, is_identifier=False, kwargs=None, label=None, order=None, widget=None):
|
||||
final_result = []
|
||||
current_view_name = get_current_view_name(request=context.request)
|
||||
for item in result:
|
||||
if item.views:
|
||||
if current_view_name in item.views:
|
||||
final_result.append(item)
|
||||
else:
|
||||
final_result.append(item)
|
||||
|
||||
return final_result
|
||||
|
||||
def __init__(self, source, attribute=None, empty_value=None, func=None, is_absolute_url=False, is_identifier=False, is_sortable=False, kwargs=None, label=None, order=None, views=None, widget=None):
|
||||
self.source = source
|
||||
self._label = label
|
||||
self.attribute = attribute
|
||||
@@ -538,10 +549,13 @@ class SourceColumn(object):
|
||||
self.func = func
|
||||
self.kwargs = kwargs or {}
|
||||
self.order = order or 0
|
||||
self.is_absolute_url = is_absolute_url
|
||||
self.is_identifier = is_identifier
|
||||
self.is_sortable = is_sortable
|
||||
self.__class__._registry.setdefault(source, [])
|
||||
self.__class__._registry[source].append(self)
|
||||
self.label = None
|
||||
self.views = views or []
|
||||
self.widget = widget
|
||||
if not attribute and not func:
|
||||
raise NavigationError(
|
||||
@@ -554,7 +568,9 @@ class SourceColumn(object):
|
||||
if not self._label:
|
||||
if self.attribute:
|
||||
try:
|
||||
attribute = resolve_attribute(obj=self.source, attribute=self.attribute)
|
||||
attribute = resolve_attribute(
|
||||
obj=self.source, attribute=self.attribute
|
||||
)
|
||||
self._label = getattr(attribute, 'short_description')
|
||||
except AttributeError:
|
||||
name, model = SourceColumn.get_attribute_recursive(
|
||||
@@ -564,11 +580,39 @@ class SourceColumn(object):
|
||||
name=name, model=model
|
||||
)
|
||||
else:
|
||||
self._label = getattr(self.func, 'short_description', _('Unnamed function'))
|
||||
self._label = getattr(
|
||||
self.func, 'short_description', _('Unnamed function')
|
||||
)
|
||||
|
||||
self.label = self._label
|
||||
|
||||
def get_sort_field_querystring(self, context):
|
||||
# We do this to get an mutable copy we can modify
|
||||
querystring = context.request.GET.copy()
|
||||
|
||||
previous_sort_field = context.get(TEXT_SORT_FIELD_VARIABLE_NAME, None)
|
||||
previous_sort_order = context.get(
|
||||
TEXT_SORT_ORDER_VARIABLE_NAME, TEXT_SORT_ORDER_CHOICE_DESCENDING
|
||||
)
|
||||
|
||||
if previous_sort_field != self.attribute:
|
||||
sort_order = TEXT_SORT_ORDER_CHOICE_ASCENDING
|
||||
else:
|
||||
if previous_sort_order == TEXT_SORT_ORDER_CHOICE_DESCENDING:
|
||||
sort_order = TEXT_SORT_ORDER_CHOICE_ASCENDING
|
||||
else:
|
||||
sort_order = TEXT_SORT_ORDER_CHOICE_DESCENDING
|
||||
|
||||
querystring[TEXT_SORT_FIELD_PARAMETER] = self.attribute
|
||||
querystring[TEXT_SORT_ORDER_PARAMETER] = sort_order
|
||||
|
||||
return '?{}'.format(querystring.urlencode())
|
||||
|
||||
def resolve(self, context):
|
||||
if self.views:
|
||||
if get_current_view_name(request=context.request) not in self.views:
|
||||
return
|
||||
|
||||
if self.attribute:
|
||||
result = resolve_attribute(
|
||||
obj=context['object'], attribute=self.attribute,
|
||||
@@ -579,7 +623,7 @@ class SourceColumn(object):
|
||||
|
||||
if self.widget:
|
||||
widget_instance = self.widget()
|
||||
return widget_instance.render(name='asd', value=result)
|
||||
return widget_instance.render(name=self.attribute, value=result)
|
||||
|
||||
if not result:
|
||||
if self.empty_value:
|
||||
@@ -600,7 +644,7 @@ class Text(Link):
|
||||
self.view = None
|
||||
|
||||
def resolve(self, *args, **kwargs):
|
||||
result = ResolvedLink(current_view=None, link=self)
|
||||
result = ResolvedLink(current_view_name=None, link=self)
|
||||
result.context = kwargs.get('context')
|
||||
result.text_span = True
|
||||
return result
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
TEXT_ONLY_IDENTIFIER = 'only_identifier'
|
||||
TEXT_EXCLUDE_IDENTIFIER = 'exclude_identifier'
|
||||
@@ -4,7 +4,6 @@ from django.template import Library
|
||||
|
||||
from ..classes import Link, Menu, SourceColumn
|
||||
from ..forms import MultiItemForm
|
||||
from ..literals import TEXT_EXCLUDE_IDENTIFIER, TEXT_ONLY_IDENTIFIER
|
||||
|
||||
register = Library()
|
||||
|
||||
@@ -48,16 +47,13 @@ def get_multi_item_links_form(context, object_list):
|
||||
return ''
|
||||
|
||||
|
||||
@register.filter
|
||||
def get_source_columns(source, args=None):
|
||||
exclude_identifier = False
|
||||
only_identifier = False
|
||||
@register.simple_tag(takes_context=True)
|
||||
def get_sort_field_querystring(context, column):
|
||||
return column.get_sort_field_querystring(context=context)
|
||||
|
||||
if args == TEXT_EXCLUDE_IDENTIFIER:
|
||||
exclude_identifier = True
|
||||
elif args == TEXT_ONLY_IDENTIFIER:
|
||||
only_identifier = True
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def get_source_columns(context, source, exclude_identifier=False, only_identifier=False):
|
||||
try:
|
||||
# Is it a query set?
|
||||
source = source.model
|
||||
@@ -77,7 +73,7 @@ def get_source_columns(source, args=None):
|
||||
pass
|
||||
|
||||
return SourceColumn.get_for_source(
|
||||
source=source, exclude_identifier=exclude_identifier,
|
||||
context=context, source=source, exclude_identifier=exclude_identifier,
|
||||
only_identifier=only_identifier
|
||||
)
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from django.apps import apps
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.urls import Resolver404, resolve
|
||||
|
||||
from mayan.apps.permissions import Permission
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_cascade_condition(app_label, model_name, object_permission, view_permission=None):
|
||||
"""
|
||||
@@ -38,3 +43,20 @@ def get_cascade_condition(app_label, model_name, object_permission, view_permiss
|
||||
return queryset.count() > 0
|
||||
|
||||
return condition
|
||||
|
||||
|
||||
def get_current_view_name(request):
|
||||
current_path = request.META['PATH_INFO']
|
||||
|
||||
# Get sources: view name, view objects
|
||||
try:
|
||||
current_view_name = resolve(current_path).view_name
|
||||
except Resolver404:
|
||||
# Can't figure out which view corresponds to this URL.
|
||||
# Most likely it is an invalid URL.
|
||||
logger.warning(
|
||||
'Can\'t figure out which view corresponds to this '
|
||||
'URL: %s; aborting menu resolution.', current_path
|
||||
)
|
||||
else:
|
||||
return current_view_name
|
||||
|
||||
@@ -43,7 +43,7 @@ from .widgets import widget_document_tags
|
||||
|
||||
class TagsApp(MayanAppConfig):
|
||||
app_namespace = 'tags'
|
||||
app_urls = 'tags'
|
||||
app_url = 'tags'
|
||||
has_rest_api = True
|
||||
has_tests = True
|
||||
name = 'mayan.apps.tags'
|
||||
@@ -101,7 +101,8 @@ class TagsApp(MayanAppConfig):
|
||||
)
|
||||
|
||||
SourceColumn(
|
||||
attribute='label', is_identifier=True, source=DocumentTag,
|
||||
attribute='label', is_identifier=True, is_sortable=True,
|
||||
source=DocumentTag,
|
||||
)
|
||||
SourceColumn(
|
||||
attribute='get_preview_widget', source=DocumentTag
|
||||
@@ -121,7 +122,8 @@ class TagsApp(MayanAppConfig):
|
||||
)
|
||||
|
||||
SourceColumn(
|
||||
attribute='label', is_identifier=True, source=Tag
|
||||
attribute='label', is_identifier=True, is_sortable=True,
|
||||
source=Tag
|
||||
)
|
||||
SourceColumn(
|
||||
attribute='get_preview_widget', source=Tag
|
||||
|
||||
Reference in New Issue
Block a user