Repository: django-cms/django-filer

Issue:
Slow directory listing in admin for folder with lots of files
I've run into a performance issue with the admin page when viewing a folder that contain lots of files (hundreds, but not thousands). It's fine if the current user is a super user, but for a non super it slows to a crawl, taking upwards of 30 seconds to load a page.

I think I tracked this down to be caused by [this line](https://github.com/divio/django-filer/blob/1.2.0/filer/admin/folderadmin.py#L366), and I think the reason is that we are doing a permissions check for every item in the list, before we give it to the paginator. But all we actually want is to know if we should show the "Change" link in `directory_table.html`, based on `item_perms.change`, so we only need to do this check for each item we are showing on the page, which is just 20. The reason it is fast for a superuser is that they skip any permissions checking that hits the database.

This change could be done either in Python, but after the paginator has chopped up the list, or in the template. I'll have a play about with it and see where I get.



Discussion:

Resolving patch:
diff --git a/filer/admin/folderadmin.py b/filer/admin/folderadmin.py
--- a/filer/admin/folderadmin.py
+++ b/filer/admin/folderadmin.py
@@ -360,8 +360,7 @@ def directory_listing(self, request, folder_id=None, viewtype=None):
             folder_files.sort()
 
         items = folder_children + folder_files
-        items_permissions = [(item, {'change': self.has_change_permission(request, item)}) for item in items]
-        paginator = Paginator(items_permissions, FILER_PAGINATE_BY)
+        paginator = Paginator(items, FILER_PAGINATE_BY)
 
         # Are we moving to clipboard?
         if request.method == 'POST' and '_save' not in request.POST:
diff --git a/filer/templatetags/filer_admin_tags.py b/filer/templatetags/filer_admin_tags.py
--- a/filer/templatetags/filer_admin_tags.py
+++ b/filer/templatetags/filer_admin_tags.py
@@ -34,3 +34,20 @@ def filer_admin_context_hidden_formfields(context):
         '<input type="hidden" name="{0}" value="{1}">',
         admin_url_params(request).items(),
     )
+
+
+@register.assignment_tag(takes_context=True)
+def filer_has_permission(context, item, action):
+    """Does the current user (taken from the request in the context) have
+    permission to do the given action on the given item.
+
+    """
+    permission_method_name = 'has_{action}_permission'.format(action=action)
+    permission_method = getattr(item, permission_method_name, None)
+    request = context.get('request')
+
+    if not permission_method or not request:
+        return False
+    # Call the permission method.
+    # This amounts to calling `item.has_X_permission(request)`
+    return permission_method(request)


Test patch:
diff --git a/filer/tests/admin.py b/filer/tests/admin.py
--- a/filer/tests/admin.py
+++ b/filer/tests/admin.py
@@ -805,7 +805,7 @@ def test_with_permissions_disabled(self):
             item_list = response.context['paginated_items'].object_list
             # user sees all items: FOO, BAR, BAZ, SAMP
             self.assertEquals(
-                set(folder.pk for folder, folder_perms in item_list),
+                set(folder.pk for folder in item_list),
                 set([self.foo_folder.pk, self.bar_folder.pk, self.baz_folder.pk,
                      self.spam_file.pk]))
 
@@ -819,7 +819,7 @@ def test_folder_ownership(self):
             # he doesn't see BAR, BAZ and SPAM because he doesn't own them
             # and no permission has been given
             self.assertEquals(
-                set(folder.pk for folder, folder_perms in item_list),
+                set(folder.pk for folder in item_list),
                 set([self.foo_folder.pk]))
 
     def test_with_permission_given_to_folder(self):
@@ -838,7 +838,7 @@ def test_with_permission_given_to_folder(self):
             item_list = response.context['paginated_items'].object_list
             # user sees 2 folder : FOO, BAR
             self.assertEquals(
-                set(folder.pk for folder, folder_perms in item_list),
+                set(folder.pk for folder in item_list),
                 set([self.foo_folder.pk, self.bar_folder.pk]))
 
     def test_with_permission_given_to_parent_folder(self):
@@ -856,7 +856,7 @@ def test_with_permission_given_to_parent_folder(self):
             item_list = response.context['paginated_items'].object_list
             # user sees all items because he has permissions on the parent folder
             self.assertEquals(
-                set(folder.pk for folder, folder_perms in item_list),
+                set(folder.pk for folder in item_list),
                 set([self.foo_folder.pk, self.bar_folder.pk, self.baz_folder.pk,
                      self.spam_file.pk]))