Repository: akvo/akvo-rsr

Issue:
Possible performance improvements?
Please close this issue if none of the below can be optimised.

Two areas I've noticed where response times seem a bit slower than the rest of the site, and could perhaps be sped up:
- The project list widget (on the grab a widget page) for organisations with more than a trivial amount of projects takes quite a while to load - it depends on the organisation but Cordaid for example takes well over 60 seconds and 504s
- The project listing page in the admin (`admin/rsr/project/`) takes slightly longer than the other options to load - ~4 seconds for me
## Test plan

GIVEN the project list widget
WHEN viewing the widget
THEN only the first 100 projects should be shown

GIVEN the project listing page in the admin
WHEN logged in as a superuser
THEN the page should load faster than 4 seconds

Note: loading the project admin page as a non-superuser hasn't changed, so still takes a while.



Discussion:
On the list widget we can try and optimise the query and rendering speeds but since there is no pagination on the widget we will always be at risk with an organisation with a lot of projects. Not really sure on how to deal with that. Possible solutions might be:
- Add pagination
- Use the API to pull in batches
- Limit number of projects in the widget (do you really want 1000 projects in that little window?)

After talk with @loicsans concluded that long term solution is pagination but short term we'll cap number of projects to 100 + add a note for when list is capped.


Resolving patch:
diff --git a/akvo/rsr/admin.py b/akvo/rsr/admin.py
--- a/akvo/rsr/admin.py
+++ b/akvo/rsr/admin.py
@@ -730,25 +730,21 @@ class ProjectAdmin(TimestampsAdminDisplayMixin, ObjectPermissionsModelAdmin, Nes
         }),
     )
     filter_horizontal = ('keywords',)
-    list_display = ('title', 'status', 'project_plan_summary', 'latest_update',
+    list_display = ('title', 'status', 'project_plan_summary', 'last_update',
                     'show_current_image', 'is_published', 'show_keywords')
     search_fields = ('title', 'subtitle', 'project_plan_summary')
     list_filter = ('currency', 'status', 'keywords',)
     # created_at and last_modified_at MUST be readonly since they have the auto_now/_add attributes
-    readonly_fields = ('budget', 'funds',  'funds_needed', 'created_at', 'last_modified_at',)
+    readonly_fields = ('budget', 'funds',  'funds_needed', 'created_at', 'last_modified_at',
+                       'last_update')
 
     def __init__(self, model, admin_site):
-        """
-        Override to add self.formfield_overrides.
-        Needed to get the ImageField working in the admin.
-        """
+        """To support ImageField override to add self.formfield_overrides."""
         self.formfield_overrides = {ImageField: {'widget': widgets.AdminFileWidget}, }
         super(ProjectAdmin, self).__init__(model, admin_site)
 
-    def get_queryset(self, request):
-        if request.user.is_superuser or request.user.is_admin:
-            return super(ProjectAdmin, self).get_queryset(request)
-
+    def get_limited_queryset(self, request):
+        """Queryset filtered on connected organisation and only for Admin and Project Editors."""
         from .models import Project
         qs = Project.objects.none()
         for employment in request.user.employers.approved():
@@ -757,6 +753,23 @@ def get_queryset(self, request):
                 qs = qs | Project.objects.filter(pk__in=project_pks)
         return qs.distinct()
 
+    def get_limited_queryset_slq_style(self, request):
+        """Slower than the non sql alternative, might be able to make it fast somehow."""
+        return super(ProjectAdmin, self).queryset(request).select_related(
+            'publishingstatus__status'
+        ).prefetch_related('keywords').filter(
+            partnerships__organisation__employees__user__id=request.user.id
+        ).filter(
+            partnerships__organisation__employees__group__name__in=['Admins', 'Project Editors']
+        )
+
+    def get_queryset(self, request):
+        """If superuser or admin return full queryset, otherwise return limited queryset."""
+        if request.user.is_superuser or request.user.is_admin:
+            return super(ProjectAdmin, self).queryset(request).select_related(
+                'publishingstatus__status').prefetch_related('keywords')
+        return self.get_limited_queryset(request)
+
     @csrf_protect_m
     @transaction.atomic
     def add_view(self, request, form_url='', extra_context=None):


Test patch: