diff --git a/readthedocs/core/templatetags/privacy_tags.py b/readthedocs/core/templatetags/privacy_tags.py index 938438efdc9..115bb9eadd2 100644 --- a/readthedocs/core/templatetags/privacy_tags.py +++ b/readthedocs/core/templatetags/privacy_tags.py @@ -20,21 +20,11 @@ def is_admin(user, project): @register.simple_tag(takes_context=True) def get_public_projects(context, user): - # Creates a Subquery object which returns the latest builds 'id'. - # Used for optimization purpose. - subquery = Subquery( - Build.objects.filter( - project=OuterRef('project_id')).values_list('id', flat=True)[:1] - ) - # Filters the latest builds of projects. - latest_build = Prefetch('builds', Build.objects.filter( - pk__in=subquery), to_attr='_latest_build' - ) # 'Exists()' checks if the project has any good builds. projects = Project.objects.for_user_and_viewer( user=user, viewer=context['request'].user, - ).prefetch_related('users', latest_build).annotate( + ).prefetch_latest_build().annotate( _good_build=Exists( Build.objects.filter(success=True, project=OuterRef('pk'))) ) diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 04e092e7f10..6fe3dcd3de1 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -392,6 +392,9 @@ class Project(models.Model): objects = ProjectQuerySet.as_manager() all_objects = models.Manager() + # Property used for storing the latest build for a project when prefetching + LATEST_BUILD_CACHE = '_latest_build' + class Meta: ordering = ('slug',) permissions = ( @@ -857,7 +860,7 @@ def get_latest_build(self, finished=True): """ # Check if there is `_latest_build` attribute in the Queryset. # Used for Database optimization. - if hasattr(self, '_latest_build'): + if hasattr(self, self.LATEST_BUILD_CACHE): if self._latest_build: return self._latest_build[0] return None diff --git a/readthedocs/projects/querysets.py b/readthedocs/projects/querysets.py index ece4e99b961..7ea7249ae0d 100644 --- a/readthedocs/projects/querysets.py +++ b/readthedocs/projects/querysets.py @@ -1,7 +1,7 @@ """Project model QuerySet classes.""" from django.db import models -from django.db.models import Q +from django.db.models import Q, OuterRef, Subquery, Prefetch from guardian.shortcuts import get_objects_for_user from readthedocs.core.utils.extend import SettingsOverrideObject @@ -75,10 +75,32 @@ def is_active(self, project): return True + def prefetch_latest_build(self): + """ + For a given queryset of projects, prefetch the latest build for each project + + This should come after any filtering. + """ + from readthedocs.builds.models import Build + + # Prefetch the latest build for each project. + subquery = Subquery( + Build.objects.filter( + project=OuterRef('project_id') + ).order_by('-date').values_list('id', flat=True)[:1] + ) + latest_build = Prefetch( + 'builds', + Build.objects.filter(pk__in=subquery), + to_attr=self.model.LATEST_BUILD_CACHE, + ) + return self.prefetch_related(latest_build) + # Aliases def dashboard(self, user=None): - return self.for_admin_user(user) + """Get the projects for this user including the latest build""" + return self.for_admin_user(user).prefetch_latest_build() def api(self, user=None, detail=True): if detail: diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py index d360fecd408..3b3522ae40f 100644 --- a/readthedocs/projects/views/private.py +++ b/readthedocs/projects/views/private.py @@ -7,7 +7,6 @@ from django.contrib import messages from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User -from django.db.models import Count, OuterRef, Subquery from django.http import ( Http404, HttpResponseBadRequest, @@ -92,14 +91,7 @@ def validate_primary_email(self, user): notification.send() def get_queryset(self): - # Filters the builds for a perticular project. - builds = Build.objects.filter( - project=OuterRef('pk'), type='html', state='finished') - # Creates a Subquery object which returns - # the value of Build.success of the latest build. - sub_query = Subquery(builds.values('success')[:1]) - return Project.objects.dashboard(self.request.user).annotate( - build_count=Count('builds'), latest_build_success=sub_query) + return Project.objects.dashboard(self.request.user) def get(self, request, *args, **kwargs): self.validate_primary_email(request.user) diff --git a/readthedocs/templates/projects/project_dashboard_base.html b/readthedocs/templates/projects/project_dashboard_base.html index 7a18d58bb0b..9574644244e 100644 --- a/readthedocs/templates/projects/project_dashboard_base.html +++ b/readthedocs/templates/projects/project_dashboard_base.html @@ -66,27 +66,22 @@

{% trans "Projects" %}

{% block project-name %} {{ project.name }} {% endblock %} - {% with builds=project.build_count %} - {% if builds == 0 %} - - {% trans "No builds yet" %} - - {% else %} - - - {% blocktrans trimmed count counter=builds %} - 1 build - {% plural %} - {{ builds }} builds - {% endblocktrans %} - - {% if project.latest_build_success %} + {% with build=project.get_latest_build %} + + + {% if build %} + + {% if build.success %} {% trans "passing" %} {% else %} {% trans "failing" %} {% endif %} - - {% endif %} + {% else %} + {% trans "No builds yet" %} + {% endif %} + {% endwith %}