Add extensible data filtering feature allowing to remove hard coded listing of documents with missing required metadata. Closes gl-issue #178.

This commit is contained in:
Roberto Rosario
2015-08-24 17:33:24 -04:00
parent 4dc3978a78
commit 1f32aa3c0b
10 changed files with 163 additions and 35 deletions

View File

@@ -19,9 +19,10 @@ from .handlers import (
from .links import ( from .links import (
link_about, link_current_user_details, link_current_user_edit, link_about, link_current_user_details, link_current_user_edit,
link_current_user_locale_profile_details, link_current_user_locale_profile_details,
link_current_user_locale_profile_edit, link_license, link_setup, link_tools link_current_user_locale_profile_edit, link_filters, link_license,
link_setup, link_tools
) )
from .menus import menu_facet, menu_main, menu_secondary from .menus import menu_facet, menu_main, menu_secondary, menu_tools
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -97,6 +98,10 @@ class CommonApp(MayanAppConfig):
) )
) )
menu_tools.bind_links(
links=(link_filters,)
)
post_save.connect( post_save.connect(
user_locale_profile_create, user_locale_profile_create,
dispatch_uid='user_locale_profile_create', dispatch_uid='user_locale_profile_create',

View File

@@ -1,8 +1,12 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.core.exceptions import PermissionDenied
from django.db import models from django.db import models
from django.utils.translation import ugettext from django.utils.translation import ugettext
from acls.models import AccessControlList
from permissions import Permission
class ModelAttribute(object): class ModelAttribute(object):
__registry = {} __registry = {}
@@ -95,3 +99,53 @@ class MissingItem(object):
self.description = description self.description = description
self.view = view self.view = view
self.__class__._registry.append(self) self.__class__._registry.append(self)
class Filter(object):
_registry = {}
@classmethod
def get(cls, slug):
return cls._registry[slug]
@classmethod
def all(cls):
return cls._registry
def __init__(self, label, slug, filter_kwargs, model, object_permission=None, hide_links=False):
self.label = label
self.slug = slug
self.filter_kwargs = filter_kwargs
self.model = model
self.object_permission = object_permission
self.hide_links = hide_links
self.__class__._registry[self.slug] = self
def __unicode__(self):
return unicode(self.label)
def get_queryset(self, user):
queryset = self.model.objects.all()
for kwargs in self.filter_kwargs:
queryset = queryset.filter(**kwargs)
queryset = queryset.distinct()
if self.object_permission:
try:
# Check to see if the user has the permissions globally
Permission.check_permissions(
user, (self.object_permission,)
)
except PermissionDenied:
# No global permission, filter ther queryset per object +
# permission
return AccessControlList.objects.filter_by_access(
self.object_permission, user, queryset
)
else:
# Has the permission globally, return all results
return queryset
else:
return queryset

View File

@@ -9,6 +9,7 @@ from django.db import models
from django.utils.html import escape from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .classes import Filter
from .models import UserLocaleProfile from .models import UserLocaleProfile
from .utils import return_attrib from .utils import return_attrib
from .widgets import DetailSelectMultiple, DisableableSelectWidget, PlainWidget from .widgets import DetailSelectMultiple, DisableableSelectWidget, PlainWidget
@@ -134,6 +135,14 @@ class FileDisplayForm(forms.Form):
fd.close() fd.close()
class FilterForm(forms.Form):
filter_slug = forms.ChoiceField(label=_('Field'))
def __init__(self, *args, **kwargs):
super(FilterForm, self).__init__(*args, **kwargs)
self.fields['filter_slug'].choices = Filter.all().items()
class LicenseForm(FileDisplayForm): class LicenseForm(FileDisplayForm):
FILENAME = 'LICENSE' FILENAME = 'LICENSE'
DIRECTORY = [] DIRECTORY = []

View File

@@ -23,6 +23,10 @@ link_current_user_locale_profile_edit = Link(
icon='fa fa-globe', text=_('Edit locale profile'), icon='fa fa-globe', text=_('Edit locale profile'),
view='common:current_user_locale_profile_edit' view='common:current_user_locale_profile_edit'
) )
link_filters = Link(
icon='fa fa-filter', text=_('Data filters'),
view='common:filter_selection'
)
link_license = Link( link_license = Link(
icon='fa fa-book', text=_('License'), view='common:license_view' icon='fa fa-book', text=_('License'), view='common:license_view'
) )

View File

@@ -7,7 +7,8 @@ from django.views.generic import RedirectView
from .views import ( from .views import (
AboutView, CurrentUserDetailsView, CurrentUserEditView, AboutView, CurrentUserDetailsView, CurrentUserEditView,
CurrentUserLocaleProfileDetailsView, CurrentUserLocaleProfileEditView, CurrentUserLocaleProfileDetailsView, CurrentUserLocaleProfileEditView,
HomeView, LicenseView, SetupListView, ToolsListView FilterResultListView, FilterSelectView, HomeView, LicenseView,
SetupListView, ToolsListView
) )
urlpatterns = patterns( urlpatterns = patterns(
@@ -37,6 +38,14 @@ urlpatterns = patterns(
r'^user/locale/edit/$', CurrentUserLocaleProfileEditView.as_view(), r'^user/locale/edit/$', CurrentUserLocaleProfileEditView.as_view(),
name='current_user_locale_profile_edit' name='current_user_locale_profile_edit'
), ),
url(
r'^filter/select/$', FilterSelectView.as_view(),
name='filter_selection'
),
url(
r'^filter/(?P<slug>[\w-]+)/results/$', FilterResultListView.as_view(),
name='filter_results'
),
) )
urlpatterns += patterns( urlpatterns += patterns(

View File

@@ -5,15 +5,16 @@ from json import dumps
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.core.urlresolvers import reverse, reverse_lazy from django.core.urlresolvers import reverse, reverse_lazy
from django.http import HttpResponseRedirect from django.http import Http404, HttpResponseRedirect
from django.template import RequestContext from django.template import RequestContext
from django.utils import timezone, translation from django.utils import timezone, translation
from django.utils.http import urlencode from django.utils.http import urlencode
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _, ugettext
from django.views.generic import TemplateView from django.views.generic import TemplateView
from .classes import Filter
from .forms import ( from .forms import (
LicenseForm, LocaleProfileForm, LocaleProfileForm_view, FilterForm, LicenseForm, LocaleProfileForm, LocaleProfileForm_view,
UserForm, UserForm_view UserForm, UserForm_view
) )
from .generics import * # NOQA from .generics import * # NOQA
@@ -99,6 +100,45 @@ class CurrentUserLocaleProfileEditView(SingleObjectEditView):
return self.request.user.locale_profile return self.request.user.locale_profile
class FilterSelectView(SimpleView):
form_class = FilterForm
template_name = 'appearance/generic_form.html'
def get_form(self):
return FilterForm()
def get_extra_context(self):
return {
'form': self.get_form(),
'title': _('Filter selection')
}
def post(self, request, *args, **kwargs):
return HttpResponseRedirect(
reverse(
'common:filter_results',
args=(request.POST.get('filter_slug'),)
)
)
class FilterResultListView(SingleObjectListView):
def get_extra_context(self):
return {
'hide_links': self.get_filter().hide_links,
'title': _('Results for filter: %s') % self.get_filter()
}
def get_filter(self):
try:
return Filter.get(self.kwargs['slug'])
except KeyError:
raise Http404(ugettext('Filter not found'))
def get_queryset(self):
return self.get_filter().get_queryset(user=self.request.user)
class HomeView(TemplateView): class HomeView(TemplateView):
template_name = 'appearance/home.html' template_name = 'appearance/home.html'

View File

@@ -12,11 +12,12 @@ from common import (
MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary, MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary,
menu_setup, menu_sidebar, menu_tools menu_setup, menu_sidebar, menu_tools
) )
from common.classes import ModelAttribute from common.classes import ModelAttribute, Filter
from common.widgets import two_state_template from common.widgets import two_state_template
from documents.models import Document, DocumentType from documents.models import Document, DocumentType
from documents.search import document_search from documents.search import document_search
from documents.signals import post_document_type_change from documents.signals import post_document_type_change
from documents.permissions import permission_document_view
from mayan.celery import app from mayan.celery import app
from navigation import SourceColumn from navigation import SourceColumn
from rest_api.classes import APIEndPoint from rest_api.classes import APIEndPoint
@@ -36,7 +37,6 @@ from .links import (
link_setup_document_type_metadata_required, link_setup_document_type_metadata_required,
link_setup_metadata_type_create, link_setup_metadata_type_delete, link_setup_metadata_type_create, link_setup_metadata_type_delete,
link_setup_metadata_type_edit, link_setup_metadata_type_list, link_setup_metadata_type_edit, link_setup_metadata_type_list,
link_documents_missing_required_metadata
) )
from .models import DocumentMetadata, DocumentTypeMetadataType, MetadataType from .models import DocumentMetadata, DocumentTypeMetadataType, MetadataType
from .permissions import ( from .permissions import (
@@ -60,6 +60,40 @@ class MetadataApp(MayanAppConfig):
'metadata_value_of', DocumentMetadataHelper.constructor 'metadata_value_of', DocumentMetadataHelper.constructor
) )
Filter(
label=_('Documents missing required metadata'),
slug='documents-no-required-metadata',
filter_kwargs=[
{
'document_type__metadata__required': True,
},
{
'metadata__value__isnull': True
},
{
'is_stub': False
}
], model=Document, object_permission=permission_document_view,
hide_links=True
)
Filter(
label=_('Documents missing optional metadata'),
slug='documents-no-optional-metadata',
filter_kwargs=[
{
'document_type__metadata__required': False,
},
{
'metadata__value__isnull': True
},
{
'is_stub': False
}
], model=Document, object_permission=permission_document_view,
hide_links=True
)
ModelAttribute( ModelAttribute(
Document, 'metadata', type_name='related', Document, 'metadata', type_name='related',
description=_( description=_(
@@ -165,9 +199,6 @@ class MetadataApp(MayanAppConfig):
'metadata:metadata_remove', 'metadata:metadata_view' 'metadata:metadata_remove', 'metadata:metadata_view'
) )
) )
menu_tools.bind_links(
links=(link_documents_missing_required_metadata,)
)
post_delete.connect( post_delete.connect(
post_document_type_metadata_type_delete, post_document_type_metadata_type_delete,

View File

@@ -12,10 +12,6 @@ from .permissions import (
permission_metadata_type_edit, permission_metadata_type_view permission_metadata_type_edit, permission_metadata_type_view
) )
link_documents_missing_required_metadata = Link(
icon='fa fa-edit', text=_('Missing metadata'),
view='metadata:documents_missing_required_metadata'
)
link_metadata_add = Link( link_metadata_add = Link(
permissions=(permission_metadata_document_add,), text=_('Add metadata'), permissions=(permission_metadata_document_add,), text=_('Add metadata'),
view='metadata:metadata_add', args='object.pk' view='metadata:metadata_add', args='object.pk'

View File

@@ -12,7 +12,6 @@ from .api_views import (
from .views import ( from .views import (
DocumentMetadataListView, MetadataTypeCreateView, MetadataTypeDeleteView, DocumentMetadataListView, MetadataTypeCreateView, MetadataTypeDeleteView,
MetadataTypeEditView, MetadataTypeListView, MetadataTypeEditView, MetadataTypeListView,
MissingRequiredMetadataDocumentListView,
SetupDocumentTypeMetadataOptionalView, SetupDocumentTypeMetadataOptionalView,
SetupDocumentTypeMetadataRequiredView SetupDocumentTypeMetadataRequiredView
) )
@@ -69,12 +68,6 @@ urlpatterns = patterns(
SetupDocumentTypeMetadataRequiredView.as_view(), SetupDocumentTypeMetadataRequiredView.as_view(),
name='setup_document_type_metadata_required' name='setup_document_type_metadata_required'
), ),
url(
r'^tools/missing_required_metadata/$',
MissingRequiredMetadataDocumentListView.as_view(),
name='documents_missing_required_metadata'
),
) )
api_urls = patterns( api_urls = patterns(

View File

@@ -35,19 +35,6 @@ from .permissions import (
) )
class MissingRequiredMetadataDocumentListView(DocumentListView):
extra_context = {
'hide_links': True,
'title': _('Documents missing required metadata'),
}
def get_document_queryset(self):
return Document.objects.filter(
document_type__metadata__required=True,
metadata__value__isnull=True
)
def metadata_edit(request, document_id=None, document_id_list=None): def metadata_edit(request, document_id=None, document_id_list=None):
if document_id: if document_id:
document_id_list = unicode(document_id) document_id_list = unicode(document_id)