Add ACLs to document indexes. Convert all document indexing views to CBV. Remove the document index setup permission. Add view tests to the document indexing app. Use MPTT methods and stop relying on undocumented API.

This commit is contained in:
Roberto Rosario
2016-03-19 02:51:45 -04:00
parent 286a6ba9b8
commit 7cde1fe78f
8 changed files with 287 additions and 166 deletions

View File

@@ -6,6 +6,10 @@ from django.apps import apps
from django.db.models.signals import post_save, post_delete from django.db.models.signals import post_save, post_delete
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from acls import ModelPermission
from acls.links import link_acl_list
from acls.permissions import permission_acl_edit, permission_acl_view
from common import ( from common import (
MayanAppConfig, menu_facet, menu_main, menu_object, menu_secondary, MayanAppConfig, menu_facet, menu_main, menu_object, menu_secondary,
menu_setup, menu_tools menu_setup, menu_tools
@@ -29,6 +33,11 @@ from .links import (
link_template_node_create, link_template_node_delete, link_template_node_create, link_template_node_delete,
link_template_node_edit link_template_node_edit
) )
from .permissions import (
permission_document_indexing_create, permission_document_indexing_delete,
permission_document_indexing_edit, permission_document_indexing_view,
permission_document_indexing_rebuild
)
from .widgets import get_instance_link, index_instance_item_link, node_level from .widgets import get_instance_link, index_instance_item_link, node_level
@@ -51,6 +60,7 @@ class DocumentIndexingApp(MayanAppConfig):
) )
DocumentIndexInstanceNode = self.get_model('DocumentIndexInstanceNode') DocumentIndexInstanceNode = self.get_model('DocumentIndexInstanceNode')
Index = self.get_model('Index') Index = self.get_model('Index')
IndexInstance = self.get_model('IndexInstance') IndexInstance = self.get_model('IndexInstance')
IndexInstanceNode = self.get_model('IndexInstanceNode') IndexInstanceNode = self.get_model('IndexInstanceNode')
@@ -58,6 +68,16 @@ class DocumentIndexingApp(MayanAppConfig):
APIEndPoint(app=self, version_string='1') APIEndPoint(app=self, version_string='1')
ModelPermission.register(
model=Index, permissions=(
permission_acl_edit, permission_acl_view,
permission_document_indexing_create,
permission_document_indexing_delete,
permission_document_indexing_edit,
permission_document_indexing_view,
)
)
Package(label='Django MPTT', license_text=''' Package(label='Django MPTT', license_text='''
Django MPTT Django MPTT
----------- -----------
@@ -179,7 +199,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
menu_object.bind_links( menu_object.bind_links(
links=( links=(
link_index_setup_edit, link_index_setup_view, link_index_setup_edit, link_index_setup_view,
link_index_setup_document_types, link_index_setup_delete link_index_setup_document_types, link_acl_list,
link_index_setup_delete
), sources=(Index,) ), sources=(Index,)
) )
menu_object.bind_links( menu_object.bind_links(

View File

@@ -7,9 +7,8 @@ from navigation import Link
from .permissions import ( from .permissions import (
permission_document_indexing_create, permission_document_indexing_edit, permission_document_indexing_create, permission_document_indexing_edit,
permission_document_indexing_delete, permission_document_indexing_delete, permission_document_indexing_rebuild,
permission_document_indexing_rebuild_indexes, permission_document_indexing_view
permission_document_indexing_setup, permission_document_indexing_view
) )
@@ -18,23 +17,17 @@ def is_not_root_node(context):
link_document_index_list = Link( link_document_index_list = Link(
icon='fa fa-list-ul', permissions=(permission_document_view,), icon='fa fa-list-ul', text=_('Indexes'),
text=_('Indexes'), view='indexing:document_index_list', args='object.pk' view='indexing:document_index_list', args='object.pk'
)
link_index_list = Link(
permissions=(permission_document_indexing_view,), text=_('Index list'),
view='indexing:index_list'
) )
link_index_main_menu = Link( link_index_main_menu = Link(
icon='fa fa-list-ul', text=_('Indexes'), view='indexing:index_list' icon='fa fa-list-ul', text=_('Indexes'), view='indexing:index_list'
) )
link_index_setup = Link( link_index_setup = Link(
icon='fa fa-list-ul', permissions=(permission_document_indexing_setup,), icon='fa fa-list-ul', text=_('Indexes'), view='indexing:index_setup_list'
text=_('Indexes'), view='indexing:index_setup_list'
) )
link_index_setup_list = Link( link_index_setup_list = Link(
permissions=(permission_document_indexing_setup,), text=_('Indexes'), text=_('Indexes'), view='indexing:index_setup_list'
view='indexing:index_setup_list'
) )
link_index_setup_create = Link( link_index_setup_create = Link(
permissions=(permission_document_indexing_create,), text=_('Create index'), permissions=(permission_document_indexing_create,), text=_('Create index'),
@@ -50,7 +43,7 @@ link_index_setup_delete = Link(
args='resolved_object.pk' args='resolved_object.pk'
) )
link_index_setup_view = Link( link_index_setup_view = Link(
permissions=(permission_document_indexing_setup,), text=_('Tree template'), permissions=(permission_document_indexing_edit,), text=_('Tree template'),
view='indexing:index_setup_view', args='resolved_object.pk' view='indexing:index_setup_view', args='resolved_object.pk'
) )
link_index_setup_document_types = Link( link_index_setup_document_types = Link(
@@ -62,23 +55,18 @@ link_rebuild_index_instances = Link(
description=_( description=_(
'Deletes and creates from scratch all the document indexes.' 'Deletes and creates from scratch all the document indexes.'
), ),
permissions=(permission_document_indexing_rebuild_indexes,), permissions=(permission_document_indexing_rebuild,),
text=_('Rebuild indexes'), view='indexing:rebuild_index_instances' text=_('Rebuild indexes'), view='indexing:rebuild_index_instances'
) )
link_template_node_create = Link( link_template_node_create = Link(
permissions=(permission_document_indexing_setup,),
text=_('New child node'), view='indexing:template_node_create', text=_('New child node'), view='indexing:template_node_create',
args='resolved_object.pk' args='resolved_object.pk'
) )
link_template_node_edit = Link( link_template_node_edit = Link(
condition=is_not_root_node, condition=is_not_root_node, text=_('Edit'),
permissions=(permission_document_indexing_setup,), text=_('Edit'),
view='indexing:template_node_edit', args='resolved_object.pk' view='indexing:template_node_edit', args='resolved_object.pk'
) )
link_template_node_delete = Link( link_template_node_delete = Link(
condition=is_not_root_node, permissions=( condition=is_not_root_node, tags='dangerous', text=_('Delete'),
permission_document_indexing_setup, view='indexing:template_node_delete', args='resolved_object.pk'
),
tags='dangerous', text=_('Delete'), view='indexing:template_node_delete',
args='resolved_object.pk'
) )

View File

@@ -136,7 +136,7 @@ class IndexTemplateNode(MPTTModel):
def __str__(self): def __str__(self):
if self.is_root_node(): if self.is_root_node():
return ugettext('<%s Root>') % self.index return ugettext('Root')
else: else:
return self.expression return self.expression

View File

@@ -6,9 +6,6 @@ from permissions import PermissionNamespace
namespace = PermissionNamespace('document_indexing', _('Indexing')) namespace = PermissionNamespace('document_indexing', _('Indexing'))
permission_document_indexing_setup = namespace.add_permission(
name='document_index_setup', label=_('Configure document indexes')
)
permission_document_indexing_create = namespace.add_permission( permission_document_indexing_create = namespace.add_permission(
name='document_index_create', label=_('Create new document indexes') name='document_index_create', label=_('Create new document indexes')
) )
@@ -21,6 +18,6 @@ permission_document_indexing_delete = namespace.add_permission(
permission_document_indexing_view = namespace.add_permission( permission_document_indexing_view = namespace.add_permission(
name='document_index_view', label=_('View document indexes') name='document_index_view', label=_('View document indexes')
) )
permission_document_indexing_rebuild_indexes = namespace.add_permission( permission_document_indexing_rebuild = namespace.add_permission(
name='document_rebuild_indexes', label=_('Rebuild document indexes') name='document_rebuild_indexes', label=_('Rebuild document indexes')
) )

View File

@@ -0,0 +1,114 @@
from __future__ import absolute_import, unicode_literals
from documents.permissions import permission_document_view
from documents.tests.test_views import GenericDocumentViewTestCase
from user_management.tests import (
TEST_USER_USERNAME, TEST_USER_PASSWORD
)
from ..models import Index
from ..permissions import (
permission_document_indexing_create, permission_document_indexing_delete,
permission_document_indexing_edit, permission_document_indexing_view
)
TEST_INDEX_LABEL = 'test label'
TEST_INDEX_EDITED_LABEL = 'test edited label'
TEST_INDEX_SLUG = 'test_slug'
class IndexViewTestCase(GenericDocumentViewTestCase):
def test_index_create_view_no_permission(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
response = self.post(
'indexing:index_setup_create', data={
'label': TEST_INDEX_LABEL, 'slug': TEST_INDEX_SLUG
}
)
self.assertEquals(response.status_code, 403)
self.assertEqual(Index.objects.count(), 0)
def test_index_create_view_with_permission(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
self.role.permissions.add(
permission_document_indexing_create.stored_permission
)
response = self.post(
'indexing:index_setup_create', data={
'label': TEST_INDEX_LABEL, 'slug': TEST_INDEX_SLUG
}, follow=True
)
self.assertContains(response, text='created', status_code=200)
self.assertEqual(Index.objects.count(), 1)
self.assertEqual(Index.objects.first().label, TEST_INDEX_LABEL)
def test_index_delete_view_no_permission(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
index = Index.objects.create(
label=TEST_INDEX_LABEL, slug=TEST_INDEX_SLUG
)
response = self.post('indexing:index_setup_delete', args=(index.pk,))
self.assertEqual(response.status_code, 403)
self.assertEqual(Index.objects.count(), 1)
def test_index_delete_view_with_permission(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
self.role.permissions.add(
permission_document_indexing_delete.stored_permission
)
index = Index.objects.create(
label=TEST_INDEX_LABEL, slug=TEST_INDEX_SLUG
)
response = self.post(
'indexing:index_setup_delete', args=(index.pk,), follow=True
)
self.assertContains(response, text='deleted', status_code=200)
self.assertEqual(Index.objects.count(), 0)
def test_index_edit_view_no_permission(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
index = Index.objects.create(
label=TEST_INDEX_LABEL, slug=TEST_INDEX_SLUG
)
response = self.post(
'indexing:index_setup_edit', args=(index.pk,), data={
'label': TEST_INDEX_EDITED_LABEL, 'slug': TEST_INDEX_SLUG
}
)
self.assertEqual(response.status_code, 403)
index = Index.objects.get(pk=index.pk)
self.assertEqual(index.label, TEST_INDEX_LABEL)
def test_index_edit_view_with_permission(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
self.role.permissions.add(
permission_document_indexing_edit.stored_permission
)
index = Index.objects.create(
label=TEST_INDEX_LABEL, slug=TEST_INDEX_SLUG
)
response = self.post(
'indexing:index_setup_edit', args=(index.pk,), data={
'label': TEST_INDEX_EDITED_LABEL, 'slug': TEST_INDEX_SLUG
}, follow=True
)
index = Index.objects.get(pk=index.pk)
self.assertContains(response, text='update', status_code=200)
self.assertEqual(index.label, TEST_INDEX_EDITED_LABEL)

View File

@@ -11,11 +11,12 @@ from .views import (
DocumentIndexNodeListView, IndexInstanceNodeView, IndexListView, DocumentIndexNodeListView, IndexInstanceNodeView, IndexListView,
RebuildIndexesConfirmView, SetupIndexDocumentTypesView, RebuildIndexesConfirmView, SetupIndexDocumentTypesView,
SetupIndexCreateView, SetupIndexDeleteView, SetupIndexEditView, SetupIndexCreateView, SetupIndexDeleteView, SetupIndexEditView,
SetupIndexListView, SetupIndexTreeTemplateListView, TemplateNodeDeleteView SetupIndexListView, SetupIndexTreeTemplateListView, TemplateNodeCreateView,
TemplateNodeDeleteView, TemplateNodeEditView
) )
urlpatterns = patterns( urlpatterns = patterns(
'document_indexing.views', '',
url( url(
r'^setup/index/list/$', SetupIndexListView.as_view(), r'^setup/index/list/$', SetupIndexListView.as_view(),
name='index_setup_list' name='index_setup_list'
@@ -42,12 +43,12 @@ urlpatterns = patterns(
name='index_setup_document_types' name='index_setup_document_types'
), ),
url( url(
r'^setup/template/node/(?P<parent_pk>\d+)/create/child/$', r'^setup/template/node/(?P<pk>\d+)/create/child/$',
'template_node_create', name='template_node_create' TemplateNodeCreateView.as_view(), name='template_node_create'
), ),
url( url(
r'^setup/template/node/(?P<node_pk>\d+)/edit/$', 'template_node_edit', r'^setup/template/node/(?P<pk>\d+)/edit/$',
name='template_node_edit' TemplateNodeEditView.as_view(), name='template_node_edit'
), ),
url( url(
r'^setup/template/node/(?P<pk>\d+)/delete/$', r'^setup/template/node/(?P<pk>\d+)/delete/$',

View File

@@ -26,9 +26,8 @@ from .models import (
) )
from .permissions import ( from .permissions import (
permission_document_indexing_create, permission_document_indexing_delete, permission_document_indexing_create, permission_document_indexing_delete,
permission_document_indexing_edit, permission_document_indexing_edit, permission_document_indexing_rebuild,
permission_document_indexing_rebuild_indexes, permission_document_indexing_view
permission_document_indexing_setup, permission_document_indexing_view
) )
from .tasks import task_do_rebuild_all_indexes from .tasks import task_do_rebuild_all_indexes
from .widgets import node_tree from .widgets import node_tree
@@ -43,34 +42,10 @@ class SetupIndexCreateView(SingleObjectCreateView):
view_permission = permission_document_indexing_create view_permission = permission_document_indexing_create
class SetupIndexListView(SingleObjectListView):
model = Index
view_permission = permission_document_indexing_setup
def get_extra_context(self):
return {
'hide_object': True,
'title': _('Indexes'),
}
class SetupIndexEditView(SingleObjectEditView):
fields = ('label', 'slug', 'enabled')
model = Index
post_action_redirect = reverse_lazy('indexing:index_setup_list')
view_permission = permission_document_indexing_edit
def get_extra_context(self):
return {
'object': self.get_object(),
'title': _('Edit index: %s') % self.get_object(),
}
class SetupIndexDeleteView(SingleObjectDeleteView): class SetupIndexDeleteView(SingleObjectDeleteView):
model = Index model = Index
post_action_redirect = reverse_lazy('indexing:index_setup_list') post_action_redirect = reverse_lazy('indexing:index_setup_list')
view_permission = permission_document_indexing_delete object_permission = permission_document_indexing_delete
def get_extra_context(self): def get_extra_context(self):
return { return {
@@ -79,8 +54,82 @@ class SetupIndexDeleteView(SingleObjectDeleteView):
} }
class SetupIndexEditView(SingleObjectEditView):
fields = ('label', 'slug', 'enabled')
model = Index
post_action_redirect = reverse_lazy('indexing:index_setup_list')
object_permission = permission_document_indexing_edit
def get_extra_context(self):
return {
'object': self.get_object(),
'title': _('Edit index: %s') % self.get_object(),
}
class SetupIndexListView(SingleObjectListView):
model = Index
object_permission = permission_document_indexing_view
def get_extra_context(self):
return {
'hide_object': True,
'title': _('Indexes'),
}
class SetupIndexDocumentTypesView(AssignRemoveView):
decode_content_type = True
left_list_title = _('Available document types')
object_permission = permission_document_indexing_edit
right_list_title = _('Document types linked')
def add(self, item):
self.get_object().document_types.add(item)
def get_document_queryset(self):
queryset = DocumentType.objects.all()
try:
Permission.check_permissions(
self.request.user, (permission_document_view,)
)
except PermissionDenied:
queryset = AccessControlList.objects.filter_by_access(
permission_document_view, self.request.user, queryset
)
return queryset
def get_extra_context(self):
return {
'object': self.get_object(),
'title': _(
'Document types linked to index: %s'
) % self.get_object()
}
def get_object(self):
return get_object_or_404(Index, pk=self.kwargs['pk'])
def left_list(self):
return AssignRemoveView.generate_choices(
self.get_document_queryset().exclude(
id__in=self.get_object().document_types.all()
)
)
def remove(self, item):
self.get_object().document_types.remove(item)
def right_list(self):
return AssignRemoveView.generate_choices(
self.get_document_queryset() & self.get_object().document_types.all()
)
class SetupIndexTreeTemplateListView(SingleObjectListView): class SetupIndexTreeTemplateListView(SingleObjectListView):
view_permission = permission_document_indexing_setup object_permission = permission_document_indexing_edit
def get_index(self): def get_index(self):
return get_object_or_404(Index, pk=self.kwargs['pk']) return get_object_or_404(Index, pk=self.kwargs['pk'])
@@ -98,116 +147,46 @@ class SetupIndexTreeTemplateListView(SingleObjectListView):
'title': _('Tree template nodes for index: %s') % self.get_index(), 'title': _('Tree template nodes for index: %s') % self.get_index(),
} }
class TemplateNodeCreateView(SingleObjectCreateView):
form_class = IndexTemplateNodeForm
model = IndexTemplateNode
class SetupIndexDocumentTypesView(AssignRemoveView): def dispatch(self, request, *args, **kwargs):
decode_content_type = True try:
object_permission = permission_document_indexing_edit Permission.check_permissions(
left_list_title = _('Available document types') request.user, (permission_document_indexing_edit,)
right_list_title = _('Document types linked')
def add(self, item):
self.get_object().document_types.add(item)
def get_object(self):
return get_object_or_404(Index, pk=self.kwargs['pk'])
def left_list(self):
return AssignRemoveView.generate_choices(
DocumentType.objects.exclude(
pk__in=self.get_object().document_types.all()
) )
except PermissionDenied:
AccessControlList.objects.check_access(
permission_document_indexing_edit, request.user,
self.get_parent_node().index
) )
def right_list(self): return super(
return AssignRemoveView.generate_choices( TemplateNodeCreateView, self
self.get_object().document_types.all() ).dispatch(request, *args, **kwargs)
)
def remove(self, item):
self.get_object().document_types.remove(item)
def get_extra_context(self): def get_extra_context(self):
return { return {
'object': self.get_object(), 'index': self.get_parent_node().index,
'title': _( 'navigation_object_list': ('index',),
'Document types linked to index: %s' 'title': _('Create child node of: %s') % self.get_parent_node(),
) % self.get_object()
} }
def get_initial(self):
parent_node = self.get_parent_node()
return {
'index': parent_node.index, 'parent': parent_node
}
# Node views def get_parent_node(self):
def template_node_create(request, parent_pk): return get_object_or_404(IndexTemplateNode, pk=self.kwargs['pk'])
parent_node = get_object_or_404(IndexTemplateNode, pk=parent_pk)
try:
Permission.check_permissions(
request.user, (permission_document_indexing_edit,)
)
except PermissionDenied:
AccessControlList.objects.check_access(
permission_document_indexing_edit, request.user, parent_node.index
)
if request.method == 'POST':
form = IndexTemplateNodeForm(request.POST)
if form.is_valid():
node = form.save()
messages.success(
request, _('Index template node created successfully.')
)
return HttpResponseRedirect(
reverse('indexing:index_setup_view', args=(node.index.pk,))
)
else:
form = IndexTemplateNodeForm(
initial={'index': parent_node.index, 'parent': parent_node}
)
return render_to_response('appearance/generic_form.html', {
'form': form,
'index': parent_node.index,
'navigation_object_list': ('index',),
'title': _('Create child node'),
}, context_instance=RequestContext(request))
def template_node_edit(request, node_pk):
node = get_object_or_404(IndexTemplateNode, pk=node_pk)
try:
Permission.check_permissions(
request.user, (permission_document_indexing_edit,)
)
except PermissionDenied:
AccessControlList.objects.check_access(
permission_document_indexing_edit, request.user, node.index
)
if request.method == 'POST':
form = IndexTemplateNodeForm(request.POST, instance=node)
if form.is_valid():
form.save()
messages.success(
request, _('Index template node edited successfully')
)
return HttpResponseRedirect(
reverse('indexing:index_setup_view', args=(node.index.pk,))
)
else:
form = IndexTemplateNodeForm(instance=node)
return render_to_response('appearance/generic_form.html', {
'form': form,
'index': node.index,
'navigation_object_list': ('index', 'node'),
'node': node,
'title': _('Edit index template node: %s') % node,
}, context_instance=RequestContext(request))
class TemplateNodeDeleteView(SingleObjectDeleteView): class TemplateNodeDeleteView(SingleObjectDeleteView):
model = IndexTemplateNode model = IndexTemplateNode
view_permission = permission_document_indexing_edit object_permission = permission_document_indexing_edit
object_permission_related = 'index'
def get_extra_context(self): def get_extra_context(self):
return { return {
@@ -225,6 +204,28 @@ class TemplateNodeDeleteView(SingleObjectDeleteView):
) )
class TemplateNodeEditView(SingleObjectEditView):
form_class = IndexTemplateNodeForm
model = IndexTemplateNode
object_permission = permission_document_indexing_edit
object_permission_related = 'index'
def get_extra_context(self):
return {
'index': self.get_object().index,
'navigation_object_list': ('index', 'node'),
'node': self.get_object(),
'title': _(
'Edit the index template node: %s?'
) % self.get_object(),
}
def get_post_action_redirect(self):
return reverse(
'indexing:index_setup_view', args=(self.get_object().index.pk,)
)
class IndexListView(SingleObjectListView): class IndexListView(SingleObjectListView):
object_permission = permission_document_indexing_view object_permission = permission_document_indexing_view
queryset = IndexInstance.objects.filter(enabled=True) queryset = IndexInstance.objects.filter(enabled=True)
@@ -341,7 +342,7 @@ class RebuildIndexesConfirmView(ConfirmView):
'message': _('On large databases this operation may take some time to execute.'), 'message': _('On large databases this operation may take some time to execute.'),
'title': _('Rebuild all indexes?'), 'title': _('Rebuild all indexes?'),
} }
view_permission = permission_document_indexing_rebuild_indexes view_permission = permission_document_indexing_rebuild
def get_post_action_redirect(self): def get_post_action_redirect(self):
return reverse('common:tools_list') return reverse('common:tools_list')

View File

@@ -49,10 +49,9 @@ def node_level(node):
return mark_safe( return mark_safe(
''.join( ''.join(
[ [
'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' * ( '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' * node.get_level(),
getattr(node, node._mptt_meta.level_attr) - 1 '' if node.is_root_node() else '<i class="fa fa-level-up fa-rotate-90"></i> ',
), '' if node.is_root_node() else '<i class="fa fa-level-up fa-rotate-90"></i> ', unicode(node)
ugettext('Root') if node.is_root_node() else unicode(node)
] ]
) )
) )